foo.bar()和bar(foo)之间的差异

  • 本文关键字:foo bar 之间 python
  • 更新时间 :
  • 英文 :


考虑:

class Parent():
def __init__(self, last_name, eye_color):
self.last_name = last_name
self.eye_color = eye_color
def show_info(self):
print("Last Name - "+self.last_name)
print("Eye Color - "+self.eye_color)
billy_cyrus = Parent("Cyrus", "blue")

以上内容来自Udacity Python课程。我发现我可以使用以下任一项调用show_info,例如billy_cyrus

billy_cyrus.show_info()
Parent.show_info(billy_cyrus)

我很好奇为什么。这两种方法有区别吗?如果是的话,什么时候会使用其中一个?如果这很重要的话,我使用的是Python 3.6。

就只调用方法而言,大多数情况下没有区别。就底层机器的工作方式而言,有一点不同。

由于show_info是一个方法,因此它是类中的描述符。这意味着,当您通过一个实例访问它时,其中它没有被另一个属性遮蔽,.运算符会在描述符上调用__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!')

如果我尝试通过执行self.a = a来对__init__中的__setattr__执行正常调用,则每次都会引发一个ValueError。但是通过使用object.__setattr__,我可以绕过这个限制。或者,我可以用super().__setattr__('a', a)来获得相同的效果,或者用self.__dict__['a'] = a来获得非常相似的效果。

@Silvio Mayolo的答案还有另一个很好的例子,在这个例子中,您可能有意将类方法用作可以应用于许多对象的函数。

另一个重要的地方(尽管不是在调用方法方面)是当您使用其他常见描述符(如property)时。与方法不同,属性是数据描述符。这意味着除了__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>

顺便说一句,隐藏与__dict__中的属性同名的属性是一个很好的技巧,因为类__dict__中的数据描述符胜过实例__dict__中的条目,即使实例__dict__的条目胜过类中的非数据描述符。

哪里会变得奇怪

您可以用Python中的实例方法重写类方法。这意味着type(foo).bar(foo)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)

在这种情况下,在实例上调用方法与在类上调用方法会产生完全不同的结果。这只是因为方法是非数据描述符。如果它们是数据描述符(使用__set__方法),则分配billy_cyrus.show_info = alt_show_info.__get__(billy_cyrus, Parent)不会覆盖任何内容,而是只重定向到__set__,并在b中手动设置它billy_cyrus__dict__会被忽略,就像属性一样。

其他资源

以下是一些关于描述符的资源:

  • Python引用-描述符协议:http://python-reference.readthedocs.io/en/latest/docs/dunderdsc/
  • (官方?)描述符如何引导:https://docs.python.org/3/howto/descriptor.html

两者之间没有语义差异。这完全是风格问题。您通常会在正常使用中使用billy_cyrus.show_info(),但允许使用第二种方法的事实允许您使用Parent.show_info将该方法本身作为一级对象。如果不允许这样做,那么就不可能(或者至少,这将相当困难)做这样的事情。

function = Parent.show_info
so_many_billy_cyrus = [billy_cyrus, billy_cyrus, billy_cyrus]
map(function, so_many_billy_cyrus)

最新更新