Python方法/描述符绑定解析:无__get__时实例访问的默认行为
__get__时的默认行为解析 首先明确核心逻辑:当你用@deco装饰foo.bar时,本质是执行了foo.bar = deco(foo.bar)——也就是把类foo的bar属性替换成了一个deco类的实例。
访问foo.bar的过程
直接返回类属性中存储的那个deco实例,这和你看到的输出一致:<__main__.deco object at 0x...>。
访问foo().bar的过程
当你通过foo的实例访问bar时,Python会先在该实例的__dict__(实例自身的属性字典)里查找bar,显然找不到——因为bar是定义在类上的属性。
接下来Python会去类foo的属性字典里找bar,此时找到的是那个deco实例。关键在于:这个deco实例没有实现__get__方法,不属于Python的「描述符」,所以Python不会触发任何特殊绑定逻辑,直接把类属性里的这个deco原封不动地返回给你。
这就是为什么你看到两次输出的是同一个deco对象的内存地址——不管通过类还是实例访问,拿到的都是同一个未绑定的deco实例,完全没有和foo的实例产生关联。
补充:实现__get__后的变化
当你给deco加上__get__方法后,它就变成了一个描述符。此时通过实例访问bar时,Python会自动调用__get__方法,把当前的foo实例传进去,你在__get__里返回MethodType(self, obj),相当于把deco实例和foo实例绑定在一起,最终得到的就是一个绑定方法对象,也就是你看到的<bound method foo.bar of <__main__.foo object at 0x...>>。
完整代码回顾
未实现__get__的装饰器
import functools class deco: def __init__(self, main_func): self.main = main_func functools.update_wrapper(self, self.main) def __call__(self, obj, *args, **kwargs): print("decorated call") self.main(obj, *args, **kwargs) class foo: @deco def bar(self): print("called bar")
执行输出:
>>> foo.bar <__main__.deco object at 0x0000026BDE10F490> >>> foo().bar <__main__.deco object at 0x0000026BDE10F490>
实现__get__的装饰器
import functools from types import MethodType # 注意需要导入MethodType class deco: def __init__(self, main_func): self.main = main_func functools.update_wrapper(self, self.main) def __get__(self, obj, objtype=None): if obj is None: return self return MethodType(self, obj) def __call__(self, obj, *args, **kwargs): print("decorated call") self.main(obj, *args, **kwargs) class foo: @deco def bar(self): print("called bar")
执行输出:
>>> foo.bar <__main__.deco object at 0x000001E9C721F790> >>> foo().bar <bound method foo.bar of <__main__.foo object at 0x000001E9C7297DD0>>
内容的提问来源于stack exchange,提问作者yankovs

