You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Python的with关键字会忽略实例上的猴子补丁方法?

问题:实例级猴子补丁无法修改上下文管理器行为,类级补丁却生效的原因

想修改现有类实例(比如数据库连接对象)的上下文管理器行为,一开始尝试给实例的__enter__和__exit__做猴子补丁,但没生效;修改类(包括替换实例的__class__)却能达到预期效果。想知道with关键字这种行为的本质原因,看了PEP 343也没找到答案。

可运行示例代码

import types


class My:
    def __enter__(self):
        print('enter')

    def __exit__(self, a, b, c):
        print('exit')


def add_behavior_to_context_manager(c):  # 不生效
    c_enter = c.__enter__
    c_exit = c.__exit__

    def __enter__(self):
        print('enter!')
        c_enter()
        return c

    def __exit__(self, exc_type, exc_value, exc_tb):
        c_exit(exc_type, exc_value, exc_tb)
        print('exit!')

    c.__enter__ = types.MethodType(__enter__, c)
    c.__exit__ = types.MethodType(__exit__, c)

    return c


def add_behavior_by_modifying_class(c):  # 生效
    class MonkeyPatchedConnection(type(c)):
        def __enter__(self):
            print('enter!')
            return super().__enter__()

        def __exit__(wrapped, exc_type, exc_value, exc_tb):
            super().__exit__(exc_type, exc_value, exc_tb)
            print('exit!')

    c.__class__ = MonkeyPatchedConnection

    return c


my = add_behavior_to_context_manager(My())
print('直接调用实例方法时符合预期: ')
my.__enter__()
my.__exit__(None, None, None)

print('\nwith语句忽略了实例上的方法: ')
with add_behavior_to_context_manager(My()):
    pass

print('\nwith语句会调用类上的方法: ')
with add_behavior_by_modifying_class(My()):
    pass

输出结果

直接调用实例方法时符合预期: 
enter!
enter
exit
exit!

with语句忽略了实例上的方法: 
enter
exit

with语句会调用类上的方法: 
enter!
enter
exit
exit!

原因解析

核心原因是:Python的with语句在查找__enter__和__exit__这类特殊方法时,会直接从实例的类(及继承链)中查找,完全跳过实例自身的属性字典。

特殊方法的查找机制

Python解释器对双下划线开头结尾的特殊方法,采用了和普通方法不同的查找逻辑:

  • 普通方法调用(比如my.__enter__())会先检查实例的属性字典,如果存在绑定的方法就优先调用;
  • 但with语句触发的特殊方法调用,是解释器直接通过type(obj).__enter__(obj)的方式执行的——它直接去实例的类里找方法,根本不会看实例自身有没有绑定同名方法。

这就解释了为什么两种补丁方式结果不同:

  1. 实例级补丁:你给实例绑定的新__enter__和__exit__只存在于实例的属性字典里,with语句根本不看这里,所以还是调用类里的原方法;但手动调用实例方法时,会优先用实例上的绑定方法,所以能看到预期输出。
  2. 类级补丁(替换__class__):你创建了原类的子类并重写了特殊方法,然后把实例的__class__指向这个子类。此时type(obj)就是这个子类,with语句查找类的特殊方法时,自然会找到子类里重写的版本,因此生效。

关于修改__class__的合理性

这种方式是Python允许的操作,针对单个实例修改行为时是可行的,但要注意潜在风险:

  • 不会影响原类的其他实例;
  • 如果代码中有基于isinstance(obj, My)这类类型检查的逻辑,替换__class__后会导致检查失败;
  • 也可以考虑用代理类包裹原实例,或者用装饰器包装上下文管理器,来达到类似效果。

内容的提问来源于stack exchange,提问作者user443854

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 11:27:54