当子类化过程中方法的类型发生变化时,我偶然发现了这种奇怪的行为:
class A:
def f(self, x):
return x**2
class B(A):
@classmethod
def f(cls, x):
return x**2
如果我现在要求B.f
的类型,我会得到(据称)错误的答案:
In [37]: type(B.f)
Out[37]: method
虽然这可以按预期工作:
In [39]: type(B.__dict__["f"])
Out[39]: classmethod
(见Python 3.4和3.6。)
这只是一个错误还是有特定的原因?
属性f
和.__dict__["f"]
项之间的区别是什么?我以为他们是一样的。
在测试套件中,我试图支持要测试的类中的两种类型的方法。为了能够做到这一点,我需要知道类型,以传递正确数量的参数。如果它是一种常规方法(即self
是第一个参数),我只是明确地传递None
,无论如何都不应该在方法中使用它,因为它不依赖于实例。
也许有更好的方法来做这件事,比如打字方法的调用。但是可能会出现这种情况并不容易的情况,例如方法有*args
和**kwargs
......因此我选择了明确的类型检查,但此时却陷入困境。 / p>
答案 0 :(得分:2)
不,这不是错误,这是正常行为。在类上访问时,classmethod
会生成绑定方法。这正是classmethod
的要点,将函数绑定到您访问它的类或您访问它的实例的类。
与函数和property
对象类似,classmethod
是descriptor object,它实现了__get__
方法。访问实例或类的属性将委托给__getattribute__
method,该挂钩的默认实现不会返回它在object.__dict__[attributename]
中找到的内容;它还将通过调用descriptor.__get__()
method来绑定描述符。这是Python非常重要的一个方面,正是这种机制使得方法和属性以及其他东西的负载都有效。
classmethod
个对象,在由描述符协议绑定时,返回一个方法对象。方法对象是记录绑定到的对象的包装器,以及调用它们时调用的函数;调用方法确实使用绑定对象作为第一个参数调用底层方法:
>>> class Foo:
... pass
...
>>> def bar(*args): print(args)
...
>>> classmethod(bar).__get__(None, Foo) # decorate with classmethod and bind
<bound method bar of <class '__main__.Foo'>>
>>> method = classmethod(bar).__get__(None, Foo)
>>> method.__self__
<class '__main__.Foo'>
>>> method.__func__
<function bar at 0x1056f0e18>
>>> method()
(<class '__main__.Foo'>,)
>>> method('additional arguments')
(<class '__main__.Foo'>, 'additional arguments')
因此,为classmethod
对象返回的方法对象引用了类(__get__
的第二个参数,所有者)和原始函数。如果在实例上使用类方法,则仍会忽略第一个参数:
>>> classmethod(bar).__get__(Foo(), Foo).__self__ # called on an instance
<class '__main__.Foo'>
另一方面,函数想要将仅绑定到实例;因此,如果__get__
的第一个参数设置为None
,则只返回self
:
>>> bar.__get__(None, Foo) # access on a class
<function bar at 0x1056f0e18>
>>> bar.__get__(Foo(), Foo) # access on an instance
<bound method bar of <__main__.Foo object at 0x105833a90>>
>>> bar.__get__(Foo(), Foo).__self__
<__main__.Foo object at 0x105833160>
如果访问ClassObject.classmethod_object
会返回classmethod
对象本身,就像function
对象那样,那么你就永远无法在类上使用类方法。这是毫无意义的。
所以不,object.attribute
并不总是与object.__dict__['attribute']
相同。如果object.__dict__['attribute']
支持描述符协议,则可以调用它。