它们分开的原因来自PEP 252中的原始散文
__get__()
: 一个可使用一个或两个参数调用的函数,用于从对象中检索属性值。这也称为“绑定”操作,因为在方法描述符的情况下它可能返回“绑定方法”对象。第一个参数 X 是必须从中检索或绑定属性的对象。当 X 为 None 时,可选的第二个参数 T 应该是元对象,并且绑定操作可能会返回仅限于 T 实例的未绑定方法。当同时指定 X 和 T 时,X 应该是 T 的实例。究竟是什么由绑定操作返回取决于描述符的语义;例如,静态方法和类方法(见下文)忽略实例并改为绑定到类型。
换句话说,这两个参数允许区分“未绑定”描述符(一个调用类)和一个“绑定”描述符(一个调用实例)。您经常看到但并没有真正考虑它的一个示例是classmethod
(它使用owner
参数并忽略instance
参数)。
如果您总是使用“绑定”描述符,那么您是对的owner
,这有点多余,因为instance
应该是该类型的实例。
也许更容易看到的是用classmethod
纯 python 实现的描述符:
class MyClassmethod(object):
def __init__(self, func):
self._func = func
def __get__(self, instance, owner = None):
# instance is ignored, `owner` is bound to the first arg
return self._func.__get__(owner)
class C:
@MyClassmethod
def func(cls, x):
print(cls)
print(x)
C.func(1)
C().func(2)
OUTPUT = '''\
$ python3 t.py
<class '__main__.C'>
1
<class '__main__.C'>
2
'''
或考虑这个(有点不完整)cached_class_property
:
class cached_class_property:
def __init__(self, fget):
self.fget = fget
def __get__(self, obj, owner):
val = self.fget(owner)
setattr(owner, self.fget.__name__, val)
return val
class C:
@cached_class_property
def f(self):
print('calculating...')
return 42
print(C.f)
print(C().f)
OUTPUT = '''\
$ python3 t.py
calculating...
42
42
'''
请注意,由于 python3,“未绑定”和“绑定”方法不再是一个真正的概念,但 api 在描述符级别持续存在——特别是类上的函数不再验证实例的类型是否与所有者匹配:
class C:
def d(self):
print(self)
class D:
pass
C().d()
C.d(D())
OUTPUT = '''\
$ python3 t.py
<__main__.C object at 0x7f09576d3040>
<__main__.D object at 0x7f09576d3040>
$ python2 t.py
<__main__.C instance at 0x7efe2c8a7910>
Traceback (most recent call last):
File "t2.py", line 9, in <module>
C.d(D())
TypeError: unbound method d() must be called with C instance as first argument (got D instance instead)
'''