foo.bar()和bar(foo)之间的区别?

时间:2017-10-13 19:49:08

标签: 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。

2 个答案:

答案 0 :(得分:12)

就调用该方法而言,大部分时间都没有区别。就底层机械如何工作而言,存在一点差异。

由于show_infomethod,因此它是班级中的descriptor。这意味着,当您通过instance访问它时,attribute不会被另一个__get__遮蔽,.运算符会在描述符上调用__setattr__来创建绑定方法那个例子。绑定方法基本上是一个闭包,它在您提供的任何其他参数之前为您传递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)最终会使用相同的参数调用相同的基础函数对象。

重要的地方

有几个地方重要的是如何调用类方法。一个常见的用例是覆盖方法,但希望使用父类中提供的定义。例如,假设我有一个我做过的课程&#34; immutable&#34;通过覆盖property。我仍然可以在实例上设置属性,如下面显示的__init__方法:

class Test:
    def __init__(self, a):
        object.__setattr__(self, 'a', a)
    def __setattr__(self, name, value):
        raise ValueError('I am immutable!')

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

@Silvio Mayolo的回答有另一个很好的例子,你有意将class方法用作可以应用于许多对象的函数。

另一个重要的地方(尽管不是在调用方法方面),就是当你使用其他常用描述符,如data-descriptors时。与方法不同,属性为magic methods。这意味着除了__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)foo.bar()根本不会调用相同的基础函数。这与override a method on an instance无关,因为它们总是使用前一次调用,但它可以对普通方法调用产生很大的影响。

http://python-reference.readthedocs.io/en/latest/docs/dunderdsc/有几种方法。我觉得最直观的一个是将实例属性设置为绑定方法。以下是修改后的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&#39; __dict__会让它被忽略,就像财产一样。

其他资源

以下是关于描述符的几个资源:

答案 1 :(得分:6)

两者之间没有语义差异。这完全是一种风格问题。通常在正常使用中使用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)