就仅调用方法而言,大多数时候没有什么区别。就底层机制的工作方式而言,存在一些差异。
Since show_info
is a method, 它是一个描述符在课堂里。这意味着当您通过instance其中它没有被另一个遮蔽属性, the .
接线员呼叫__get__在描述符上为该实例创建绑定方法。绑定方法基本上是一个传入的闭包self
在您提供的任何其他参数之前为您提供参数。你可以看到绑定是这样发生的:
>>> billy_cyrus.show_info
<bound method Parent.show_info of <__main__.Parent object at 0x7f7598b14be0>>
每次使用时都会创建一个不同的闭包.
类方法上的运算符。
另一方面,如果通过类对象访问该方法,则它不会被绑定。该方法是一个描述符,它只是类的常规属性:
>>> Parent.show_info
<function __main__.Parent.show_info>
您可以在调用方法之前模拟绑定方法的确切行为,方法是调用它的__get__
你自己:
>>> bound_meth = Parent.show_info.__get__(billy_cyrus, type(billy_cyrus))
>>> bound_meth
<bound method Parent.show_info of <__main__.Parent object at 0x7f7598b14be0>>
同样,在 99.99% 的情况下,这不会对您产生任何影响,因为从功能上来说bound_meth()
and Parent.bound_meth(billy_cyrus)
最终使用相同的参数调用相同的底层函数对象。
重要的地方
有几个地方如何调用类方法很重要。一种常见的用例是当您重写方法,但想要使用父类中提供的定义时。例如,假设我有一个类,我通过重写使其“不可变”__setattr__。我仍然可以在实例上设置属性,如__init__
方法如下图所示:
class Test:
def __init__(self, a):
object.__setattr__(self, 'a', a)
def __setattr__(self, name, value):
raise ValueError('I am immutable!')
如果我尝试正常调用__setattr__
in __init__
通过做self.a = a
, a ValueError
每次都会被提高。但是通过使用object.__setattr__
,我可以绕过这个限制。或者,我可以这样做super().__setattr__('a', a)
达到同样的效果,或者self.__dict__['a'] = a
对于一个非常相似的。
@Silvio Mayolo 的答案有另一个很好的例子,你会故意想要使用类方法作为可以应用于许多对象的函数。
另一个重要的地方(尽管不是在调用方法方面)是当您使用其他常见描述符时,例如property。与方法不同,属性是数据描述符。这意味着他们定义了一个__set__
方法(以及可选的__delete__
) 此外__get__
。属性创建一个虚拟属性,其 getter 和 setter 是任意复杂的函数,而不仅仅是简单的赋值。要正确使用属性,您必须通过实例来完成。例如:
class PropDemo:
def __init__(self, x=0):
self.x = x
@property
def x(self):
return self.__dict__['x']
@x.setter
def x(self, value):
if value < 0:
raise ValueError('Not negatives, please!')
self.__dict__['x'] = value
现在你可以做类似的事情
>>> inst = PropDemo()
>>> inst.x
0
>>> inst.x = 3
>>> inst.x
3
如果您尝试通过类访问该属性,您可以获得底层描述符对象,因为它将是一个未绑定的属性:
>>> PropDemo.x
<property at 0x7f7598af00e8>
附带说明一下,隐藏与中的属性同名的属性__dict__
这是一个很有效的技巧,因为类中的数据描述符__dict__
实例中的王牌条目__dict__
,即使实例__dict__
类中的条目胜过非数据描述符。
哪里会变得奇怪
您可以在 Python 中使用实例方法覆盖类方法。这意味着type(foo).bar(foo)
and foo.bar()
根本不调用相同的底层函数。这与以下内容无关魔法方法因为它们总是使用前一个调用,但它对于正常的方法调用可能会产生很大的影响。
有几种方法可以重写实例上的方法。我发现最直观的是将实例属性设置为绑定方法。这是修改后的示例billy_cyrus
,假设定义Parent
在原来的问题中:
def alt_show_info(self):
print('Another version of', self)
billy_cyrus.show_info = alt_show_info.__get__(billy_cyrus, Parent)
在这种情况下,调用实例与类上的方法将具有完全地不同的结果。这只有效,因为方法是non-data顺便说一下描述符。如果它们是数据描述符(带有__set__
方法),赋值billy_cyrus.show_info = alt_show_info.__get__(billy_cyrus, Parent)
不会覆盖任何内容,而是只会重定向到__set__
,并在 b 中手动设置billy_cyrus
's __dict__
只会被忽略,就像属性一样。
其他资源
以下是有关描述符的一些资源:
- Python 参考 - 描述符协议:http://python-reference.readthedocs.io/en/latest/docs/dunderdsc/
- (官方?)描述符操作指南:https://docs.python.org/3/howto/descriptor.html