【问题标题】:How does python know when to pass in `self`?python如何知道何时传入`self`? [关闭]
【发布时间】:2022-01-21 04:01:24
【问题描述】:

考虑一个简单的例子:

class C:
    @staticmethod
    def my_static_method():
        print("static")

    def my_instance_method(self):
        print("self")

当我调用C().my_static_method() 时,python 不会将C 的实例传递给my_static_method,并且my_static_method 引用的descriptor 也不期望C 的实例。

这是有道理的。

但是当我调用 C().my_instance_method() 时,python 怎么知道将我调用 my_instance_methodC 的实例作为参数传递,而不需要我指定任何内容?

【问题讨论】:

  • 因为函数对象是描述符。了解它的工作原理in your own link
  • Docs on method objects: "调用x.f() 完全等同于MyClass.f(x)"
  • 为了记录,我没有投反对票。而且我认为反对票是严厉的。这不是一个坏问题。
  • 很难写出我想要的答案,只是因为我无法决定在各个技术方面应该包含多少细节。
  • 参见:eev.ee/blog/2012/05/23/python-faq-descriptors,然后是eev.ee/blog/2013/03/03/…。 “是什么让 Python 成为一种面向对象的语言?...答案是描述符,它基本上是“让自己工作的东西”。描述符对象是类的一个属性,只要在一个该类的实例。"

标签: python python-decorators python-descriptors


【解决方案1】:

正如链接所解释的,函数对象是描述符!就像staticmethod对象一样。

他们有一个__get__ 方法,该方法返回一个绑定方法对象,它基本上只是将实例本身部分应用为第一个位置参数。考虑:

>>> def foo(self):
...     return self.bar
...
>>> class Baz:
...     bar = 42
...
>>> baz = Baz()
>>> bound_method = foo.__get__(baz, Baz)
>>> bound_method
<bound method foo of <__main__.Baz object at 0x7ffcd001c7f0>>
>>> method()
42

【讨论】:

    【解决方案2】:

    通过将 @staticmethod 装饰器添加到 my_static_method,您告诉 python 不要将 C 的调用实例传递给函数。因此,您可以将此函数称为 C.my_static_method()。

    通过调用 C(),您创建了一个 C 的实例。然后您调用了非静态函数 my_instance_method(),Python 愉快地将新的 C 实例作为第一个参数传递。

    调用 C.my_instance_method() 时会发生什么?

    修辞:您将收到“缺少一个必需的 arg self”异常 - 因为 my_instance_method 仅在从实例调用时才有效,除非您将其装饰为静态。

    当然,您仍然可以从实例 C().my_static_method() 调用静态成员,但您没有 self 参数,因此无法访问该实例。

    【讨论】:

    • 你似乎没有抓住重点。问题是关于如何 Python 将实例作为参数传递给非静态方法。
    【解决方案3】:

    这里的关键是方法是恰好是类属性的函数。在 Python 中,真正的魔力发生在属性查找过程中。您提供的链接解释了earlier 每次x.y 在 Python 中发生的次数。 (请记住,一切都是对象;包括函数、类、模块、type(它是自身的一个实例)...)

    这个过程就是描述符可以工作的原因;为什么我们需要明确的self;以及为什么我们可以做一些有趣的事情,比如用普通函数调用语法调用方法(只要我们从类中查找它而不是实例),给它取别名,用@模仿方法绑定过程987654325@....

    假设我们有c = C()。当您执行c.my_instance_method(暂时不要介意调用它)时,Python 在type(c)(即C 类中)中查找my_instance_method,并检查它是否是描述符,以及它是否特别是数据描述符。函数是非数据描述符;即使在课堂之外,你也可以写

    >>> def x(spam): return spam
    ...
    >>> x.__get__
    <method-wrapper '__get__' of function object at 0x...>
    

    由于优先级规则,只要c不直接附加同名属性,就会在C中找到该函数使用其__get__时间>。请注意,有问题的 __get__ 来自类 - 但它没有使用与上面的 x.__get__ 相同的过程。该代码在类中查找,因为这是检查属性查找的地方之一;但是当c.my_instance_method 重定向到C.my_instance_method.__get__ 时,它会看到直接 - 将__get__ 属性直接附加到函数不会改变任何东西(这就是为什么staticmethod 被实现为一个类而是)。

    __get__ 实现了实际的方法绑定。假设我们在 str 类中找到了 x 作为方法:

    >>> x.__get__('spam', str)
    <bound method x of 'spam'>
    >>> x.__get__('spam', str)()
    'spam'
    

    请记住,虽然所讨论的函数需要三个参数,但我们将 __get__ 本身作为方法调用, - 所以x 以相同的方式绑定到它。等效且更忠实于实际过程:

    >>> type(x).__get__(x, 'spam', str)
    <bound method x of 'spam'>
    >>> type(x).__get__(x, 'spam', str)()
    'spam'
    

    那么“绑定方法”到底是什么?

    >>> bound = type(x).__get__(x, 'spam', str)
    >>> type(bound)
    <class 'method'>
    >>> bound.__call__
    <method-wrapper '__call__' of method object at 0x...>
    >>> bound.__func__
    <function x at 0x...>
    >>> bound.__self__
    'spam'
    >>> type(bound)(x, 'eggs')
    <bound method x of 'eggs'>
    

    几乎如您所愿:它是一个可调用对象,存储和使用原始函数和self 值,并在__call__ 中做显而易见的事情。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-06
      • 1970-01-01
      • 2021-03-03
      • 2013-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-31
      相关资源
      最近更新 更多