就调用方法而言,大多数情况下没有区别。但在底层机制的工作方式上,有一点不同。
由于show_info
是一个method,它是类中的descriptor。这意味着当你通过一个instance访问它时,在该实例中没有被另一个attribute遮蔽,.
运算符会调用描述符上的__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()
和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!')
如果我在
__init__
中尝试正常调用
__setattr__
,比如使用
self.a = a
,每次都会引发
ValueError
。但是通过使用
object.__setattr__
,我可以绕过这个限制。或者,我可以使用
super().__setattr__('a', a)
达到相同的效果,或者使用
self.__dict__['a'] = a
实现非常相似的效果。
Silvio Mayolo的答案有另一个很好的例子,您可以故意将类方法用作可应用于多个对象的函数。
另一个地方也很重要(虽然不涉及调用方法),那就是当您使用其他常见描述符
property
时。与方法不同,属性是
data-descriptors。这意味着它们除了
__get__
之外还定义了
__set__
方法(和可选的
__delete__
)。属性创建了一个虚拟属性,其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>
顺便提一下,在Python中隐藏与__dict__
属性相同名称的属性是一个巧妙的技巧,因为类中的数据描述符在__dict__
中优先于实例__dict__
条目,即使实例__dict__
条目优先于类中的非数据描述符。
可能会出现的问题
在Python中,您可以使用实例方法覆盖类方法。这意味着type(foo).bar(foo)
和foo.bar()
根本不调用同一个基础函数。对于magic methods来说这无关紧要,因为它们总是使用前者的调用方式,但对于普通方法调用来说可能会产生很大的影响。
有几种方法可以在实例上重写方法。我认为最直观的方法是将实例属性设置为绑定方法。以下是一个修改后的billy_cyrus
示例,假设原始问题中定义了Parent
:
def alt_show_info(self):
print('Another version of', self)
billy_cyrus.show_info = alt_show_info.__get__(billy_cyrus, Parent)
在这种情况下,对实例调用方法与对类调用方法将产生完全不同的结果。顺便说一下,这仅适用于方法是非数据描述符的情况。如果它们是数据描述符(具有__set__方法),那么赋值billy_cyrus.show_info = alt_show_info.__get__(billy_cyrus, Parent)将不会覆盖任何内容,而只会重定向到__set__,并且在billy_cyrus的__dict__中手动设置它将被忽略,就像属性一样。
其他资源
以下是一些关于描述符的资源:
Python参考-描述符协议:
http://python-reference.readthedocs.io/en/latest/docs/dunderdsc/
(官方?)描述符指南:
https://docs.python.org/3/howto/descriptor.html
self
参数的来源。然而,使用Class.method
或instance.method
(后者为绑定方法)传递方法作为参数(例如用于map
或回调函数)时可能会很有用。 - tobias_kParent.show_info(billy_cyrus)
就会出现问题。 - Charles Duffy