我想知道为什么method
存在两个副本,一个用于实例对象,另一个用于类对象,为什么它是这样设计的?
class Bar():
def method(self):
pass
@classmethod
def clsmethod(cls):
pass
b1 = Bar()
b2 = Bar()
print(Bar.method,id(Bar.method))
print(b1.method,id(b1.method))
print(b2.method,id(b2.method))
print(Bar.clsmethod,id(Bar.clsmethod))
print(b1.clsmethod,id(b1.clsmethod))
print(b2.clsmethod,id(b2.clsmethod))
此设计基于描述符,特别是非数据描述符。通过定义__get__
方法,每个函数恰好是一个非数据描述符:
>>> def foo():
... pass
...
>>> foo.__get__
<method-wrapper '__get__' of function object at 0x7fa75be5be50>
当代码中有表达式x.y
时,这意味着在对象x
上查找属性y
。这里解释了具体的规则,其中一个规则涉及y
是存储在x
(或任何子类(类上的(非(数据描述符。以下是一个示例:
>>> class Foo:
... def test(self):
... pass
...
这里Foo.test
在类Foo
上查找名称test
。结果是您将在全局名称空间中定义的函数:
>>> Foo.test
<function Foo.test at 0x7fa75be5bf70>
然而,正如我们上面所看到的,每个函数也是一个描述符,因此,如果您在Foo
的实例上查找test
,它将调用描述符的__get__
方法来计算结果:
>>> f = Foo()
>>> f.test
<bound method Foo.test of <__main__.Foo object at 0x7fa75bf56b20>>
我们可以通过手动调用Foo.test.__get__
:来获得类似的结果
>>> Foo.test.__get__(f, type(f))
<bound method Foo.test of <__main__.Foo object at 0x7fa75bf56b20>>
该机制确保实例(通常通过self
表示(作为第一个参数传递给实例方法。描述符返回一个绑定方法(绑定到执行查找的实例(,而不是原始函数。这个绑定方法在被调用时将实例作为第一个参数插入。每次执行Foo.test
时,都会返回一个新的绑定方法对象,因此它们的id
不同。
classmethod
s的情况与调用Foo.test.__get__(None, Foo)
的情况类似。唯一的区别是,对于实例object.__getattribute__
被调用,而对于类type.__getattribute__
优先。
>>> class Bar:
... @classmethod
... def test(cls):
... pass
...
>>> Bar.test
<bound method Bar.test of <class '__main__.Bar'>>
>>> Bar.__dict__['test'].__get__(None, Bar)
<bound method Bar.test of <class '__main__.Bar'>>