怎么没打印__get__里面的,我记得.访问属性,会访问
Python 的描述符协议(Descriptor Protocol)主要设计用于控制 类(Class) 的属性访问行为,而不是 实例(Instance) 的属性访问。
● 情况 A(你的代码):当你通过实例 d 访问 func 时,Python 会直接去 d 的 dict (实例字典)或者 Decorator 的 dict (类字典)里查找名为 func 的数据。它找到了在 init 中赋值的 self.func ,然后直接返回该值。这个过程不会触发 get 。
● 情况 B(触发 get ):当 Decorator 的实例作为另一个类的属性存在时,访问该属性才会激活描述符协议。
这是 Python 内部属性查找顺序决定的:
1. 数据描述符优先: 如果类字典里有实现了 get 和 set 的对象,它会拦截实例属性的访问。
2. 实例属性次之: 如果类字典里没有数据描述符,但实例字典( obj.dict )里有这个属性,直接返回实例属性。(你的代码属于这一层,因为 func 只是普通赋值,没有变成数据描述符去拦截自己)。
3. 非数据描述符最后: 只有实现了 get 但没有 set 的对象(非数据描述符),且实例字典里没有同名属性时,才会调用 get 。
在你的代码中, d.func 是在 init 里直接赋值给 self 的。对于 Python 来说,这就是一个普通的实例变量。Python 不会为了读取一个普通的实例变量而去检查这个变量本身是不是一个描述符。
from functools import partial class Decorator: def __init__(self, func=None, arg1=1, arg2=2, arg3=3): print(".....", func, arg1, arg2, arg3) if func is None: self.func = partial(Decorator, arg1=1, arg2=2, arg3=3) else: self.func = func def __call__(self, *args, **kwargs): return self.func(*args, **kwargs) def __get__(self, instance, owner): # 只有当 Decorator 实例作为别的类的属性时,这里才会执行 print('----------', self, instance, owner) if instance is None: return self else: return partial(self.__call__, instance) # --- 测试代码 --- # 1. 直接实例化(你的原代码) print("--- 测试 1: 直接实例化 ---") d = Decorator(1) print(d.func) # 结果:只打印了 __init__ 里的内容,没打印 __get__ # 2. 作为类属性(触发 __get__) print("\n--- 测试 2: 作为类属性 ---") class MyClass: my_decorator = Decorator(1) # 此时 Decorator 是 MyClass 的属性 obj = MyClass() print(obj.my_decorator) # 结果:这里会打印 '---------- ...',即触发了 __get__
描述符的本质:它是给“类”用的工具,不是给“实例”用的
你提到的“说明书”比喻很形象,但更准确的理解是:描述符是类的“属性管理协议”。
● 实例(Instance) 关注的是 “数据”(比如 self.name = "Alice" )。
● 类(Class) 关注的是 “行为”和“规则”(比如“访问这个属性时,我要拦截并做点处理”)。
如果允许在 实例层面 随意触发 get ,Python 的对象模型就会崩溃。
Python 的属性查找优先级(MRO 与 描述符协议)
为了解决这个问题,Python 制定了严格的 属性查找顺序。当你执行 obj.attr 时,Python 内部是这样做的:
1. 数据描述符检查: 去 类(Class) 的字典里找 attr 。如果找到了,且它有 set 或 delete (数据描述符),立刻调用它的 get 。这是最高优先级(用于实现 property)。
2. 实例字典检查: 如果类里没有数据描述符,去 实例(obj.dict) 里找 attr 。
● 关键点来了: 如果在这里找到了,直接返回值,绝不检查它是不是描述符。这就是为什么你的代码里 d.func 没有触发 get 。
3. 非数据描述符/普通类属性检查: 如果实例里也没有,再回 类(Class) 里找。如果找到了只有 get 的对象(非数据描述符,如函数、你的 Decorator),才调用 get 。
这种设计区分了 “配置” 和 “状态”:
● 作为类属性时(配置):
你把 Decorator 放在类上,意思是:“这个类的所有实例,在访问这个属性时,都要遵守这套规则。”
-> 触发 get ,实现元编程(如方法绑定、property)。
● 作为实例属性时(状态):
你把 Decorator 放在实例 d 上,意思是:“这个特定的对象 d 持有一个名为 func 的东西。”
-> 不触发 get ,把它当作普通数据存储。
一句话解释: get 是为了让 类 能够接管 实例 的属性访问权;如果你直接在 实例 上操作,那就是实例自己的私有领地,类定义的规则(描述符)默认是不插手的,除非那个规则极其强势(数据描述符且定义在类上)。