【问题标题】:How does assignment of a function as a class attribute become a method in Python?函数作为类属性的赋值如何成为 Python 中的方法?
【发布时间】:2010-02-21 22:11:43
【问题描述】:
>>> class A(object): pass
>>> def func(cls): pass
>>> A.func = func
>>> A.func
<unbound method A.func>

这个赋值如何创建一个方法?分配对类执行以下操作似乎不直观:

  • 将函数转换为未绑定的实例方法
  • classmethod() 中包装的函数转换为类方法(实际上,这很直观)
  • staticmethod() 中包装的函数转换为函数

似乎第一个应该有instancemethod(),而最后一个应该根本没有包装函数。我知道这些是在 class 块内使用的,但为什么要在它之外应用呢?

但更重要的是,将函数分配到类中究竟是如何工作的?发生了什么神奇的事情可以解决这三件事?

对此更令人困惑:

>>> A.func
<unbound method A.func>
>>> A.__dict__['func']
<function func at 0x...>

但我认为这与描述符有关,当 检索 属性时。我认为这与这里的属性设置没有太大关系。

【问题讨论】:

标签: python class-method


【解决方案1】:

你说得对,这与描述符协议有关。描述符是如何在 Python 中实现将接收者对象作为方法的第一个参数传递的。您可以从here 阅读有关 Python 属性查找的更多详细信息。以下显示了在较低级别上执行 A.func = func; 时发生的情况A.func:

# A.func = func
A.__dict__['func'] = func # This just sets the attribute
# A.func
#   The __getattribute__ method of a type object calls the __get__ method with
#   None as the first parameter and the type as the second.
A.__dict__['func'].__get__(None, A) # The __get__ method of a function object
                                    # returns an unbound method object if the
                                    # first parameter is None.
a = A()
# a.func()
#   The __getattribute__ method of object finds an attribute on the type object
#   and calls the __get__ method of it with the instance as its first parameter.
a.__class__.__dict__['func'].__get__(a, a.__class__)
#   This returns a bound method object that is actually just a proxy for
#   inserting the object as the first parameter to the function call.

因此,是在类或实例上查找函数将其转换为方法,而不是将其分配给类属性。

classmethodstaticmethod 只是稍微不同的描述符,classmethod 返回绑定到类型对象的绑定方法对象,而 staticmethod 只返回原始函数。

【讨论】:

  • 谢谢,我认为这是这里最正确的答案。我在这里找到了更详细的分析:cafepy.com/article/python_attributes_and_methods/…。这里的关键(魔术)是一个函数在被访问时变成了一个方法,而不是设置,这发生在每个函数对象上可用的__get__ 描述符上。 classmethodstaticmethod 有不同的 __get__,它们将函数“转换”为类/静态方法。碰巧函数上的默认__get__ 充当实例方法。
【解决方案2】:

Descriptors 是魔术1,当您从实例或类中检索普通函数时,它会将其转换为绑定或未绑定方法,因为它们都是需要不同绑定策略的函数. classmethodstaticmethod 装饰器实现了其他绑定策略,staticmethod 实际上只返回原始函数,这与您从非函数 callable 对象获得的行为相同。

请参阅“User-defined methods” 了解一些血腥细节,但请注意:

还要注意这种转换只发生在用户定义的函数上;其他可调用对象(以及所有不可调用对象)无需转换即可检索。

因此,如果您想对自己的可调用对象进行这种转换,您可以将其包装在一个函数中,但您也可以编写一个描述符来实现您自己的绑定策略。

这里是 staticmethod 装饰器,当它被访问时返回底层函数。

>>> @staticmethod
... def f(): pass
>>> class A(object): pass
>>> A.f = f
>>> A.f
<function f at 0x100479398>
>>> f
<staticmethod object at 0x100492750>

而具有__call__ 方法的普通对象不会被转换:

>>> class C(object):
...         def __call__(self): pass
>>> c = C() 
>>> A.c = c 
>>> A.c
<__main__.C object at 0x10048b890>
>>> c
<__main__.C object at 0x10048b890>

1具体函数为func_descr_get中的Objects/funcobject.c

【讨论】:

    【解决方案3】:

    您必须考虑的是在 Python 中 everything is an object。通过确定更容易理解正在发生的事情。如果你有一个函数def foo(bar): print bar,你可以做spam = foo并调用spam(1),当然是1

    Python 中的对象将其实例属性保存在名为__dict__ 的字典中,并带有指向其他对象的“指针”。作为functions in Python are objects as well,它们可以作为简单的变量进行分配和操作,传递给其他函数等。Python 的面向对象实现利用了这一点,并将方法视为属性,作为在__dict__ 中的函数对象。

    实例方法的first parameter is always the instance object itself,通常称为self(但也可以称为thisbanana)。当直接在class 上调用方法时,它不会绑定到任何实例,因此您必须给它一个实例对象作为第一个参数(A.func(A()))。当你调用bound function (A().func()) 时,方法的第一个参数self 是隐式的,但在幕后,Python 的作用与直接调用未绑定函数并将实例对象作为第一个参数。

    如果理解这一点,那么分配A.func = func(在幕后执行A.__dict__["func"] = func)会给您留下一个未绑定的方法,这一点不足为奇。

    在您的示例中,def func(cls): pass 中的 cls 实际上将传递的是 A 类型的实例 (self)。当您应用 classmethodstaticmethod decorators 时,只需在调用函数/方法之前获取在调用函数/方法期间获得的第一个参数,并将其转换为其他参数。

    classmethod 接受第一个参数,获取实例的class 对象,并将其作为第一个参数传递给函数调用,而staticmethod 只是丢弃第一个参数并在没有它的情况下调用函数。

    【讨论】:

      【解决方案4】:

      第 1 点:您定义的函数 func 在 Python 中以 First-Class Object 的形式存在。

      第 2 点:Python 中的类将它们的属性存储在它们的 __dict__ 中。

      那么当你在 Python 中将函数作为类属性的值传递时会发生什么?该函数存储在类的__dict__ 中,使其成为该类的方法,可通过调用分配给它的属性名称来访问。

      【讨论】:

      • 问题是如何让它成为那个类的方法? A.a = 3 没有什么特别之处?
      • @MTsoul,区别在于callable(func)True,而callable(3)False
      • @gnibbler 不是这样 — 使用 __call__ 方法创建对象不会触发此问题。
      【解决方案5】:

      关于 MTsoul 对 Gabriel Hurley 的回答的评论:

      不同的是func 有一个__call__() 方法,使其“可调用”,即您可以对其应用() 运算符。查看the Python docs(在该页面上搜索__call__)。

      【讨论】:

      • 不,具有__call__ 方法的对象不会神奇地转换为instancemethod
      • @jleedev:它不会把它变成一个实例方法,而是变成一个可调用的。作为对象属性的可调用对象确实将其转换为实例方法。
      • @voyager codepad.org/eOI0Hf88 可调用只是意味着它有一个__call__ 方法,但实际的方法绑定由描述符处理。看我的回答。
      猜你喜欢
      • 2016-05-21
      • 2021-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-25
      • 1970-01-01
      相关资源
      最近更新 更多