【问题标题】:Difference between foo.bar() and bar(foo)?foo.bar() 和 bar(foo) 之间的区别?
【发布时间】:2017-10-13 19:49:08
【问题描述】:

考虑:

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。

【问题讨论】:

  • 完全没有区别。第一个是善良的,如果是后者的语法糖。这就是self 参数的来源。但是,当您想将方法作为参数传递(例如,用于map 或用于回调)以使用Class.methodinstance.method(后者是绑定方法)时,它会很有用。跨度>
  • @tobias_k。严格来说,这不是 100% 正确的。
  • @MadPhysicist 这就是为什么它是评论而不是答案的原因。随意详细说明。
  • 我正在起草一个我们说话的答案:) 我认为这是一个重要的问题。
  • 在不知道它们的确切类的情况下处理对象很有用。如果有人想通过GrandparentGuardian 代替ParentParent.show_info(billy_cyrus) 就会崩溃。

标签: python


【解决方案1】:

就只是调用方法而言,大部分时间没有区别。就底层机制的工作方式而言,存在一些差异。

由于show_infomethod,因此在类中它是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!')

如果我尝试通过执行self.a = a__init__ 中的__setattr__ 进行正常调用,则每次都会引发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>

附带说明,隐藏与__dict__ 中的属性同名的属性是一个巧妙的技巧,因为类__dict__ 中的数据描述符胜过实例__dict__ 中的条目,即使实例@987654364 @ 条目胜过类中的非数据描述符。

哪里会变得奇怪

您可以在 Python 中使用实例方法覆盖类方法。这意味着type(foo).bar(foo)foo.bar() 根本不会调用相同的底层函数。这与magic methods 无关,因为它们总是使用前一个调用,但它可以对正常的方法调用产生很大的影响。

override a method on an instance 有几种方法。我发现最直观的方法是将实例属性设置为绑定方法。下面是一个修改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__ 会忽略它,就像属性一样。

其他资源

这里有一些关于描述符的资源:

【讨论】:

    【解决方案2】:

    两者在语义上没有区别。这完全是风格问题。您通常会在正常使用中使用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)
    

    【讨论】:

    • 如果“相当困难”的意思是“使用lambda b: b.show_info()”...但是,是的,这是一种可能更喜欢这种风格的情况。
    • 我很好奇 - 如果您打算像这样使用 show_info,您不会在类之外将其定义为独立函数而不是实例属性吗?
    • @wwii 您可能无法控制show_info 的源代码。我经常使用由对 Java 面向对象思想投入大量资金的人编写的代码库,因此每个 API 调用最终都会成为一种方法,无论好坏。
    • @MadPhysicist 我确定您的意思是“在 Python 2 中,map 等同于列表理解”。你知道,严格地迂腐 100% 正确......;-)
    • 在 Python3 中,将 [] 替换为 () 以获得等效的生成器表达式。我的错。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-03
    • 1970-01-01
    • 2017-06-23
    • 2023-02-25
    相关资源
    最近更新 更多