函数作为类属性的赋值如何成为Python中的一个方法?

How does assignment of a function as a class attribute become a method in Python?

1
2
3
4
5
>>> class A(object): pass
>>> def func(cls): pass
>>> A.func = func
>>> A.func
<unbound method A.func>

此分配如何创建方法?作业为班级做以下工作似乎毫无意义:

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

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

但更重要的是,如何准确地将函数分配到类中?什么魔法能解决这三件事?

更令人困惑的是:

1
2
3
4
>>> A.func
<unbound method A.func>
>>> A.__dict__['func']
<function func at 0x...>

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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只返回原始函数。


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

请参阅"用户定义的方法"了解一些详细信息,但请注意:

Also notice that this transformation only happens for user-defined functions; other callable objects (and all non-callable objects) are retrieved without transformation.

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

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

1
2
3
4
5
6
7
8
>>> @staticmethod
... def f(): pass
>>> class A(object): pass
>>> A.f = f
>>> A.f
<function f at 0x100479398>
>>> f
<staticmethod object at 0x100492750>

而使用__call__方法的普通对象不会被转换:

1
2
3
4
5
6
7
8
>>> 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具体功能是objects/funcobject.c中的func_descr_get


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

python中的对象将其实例属性保存在一个名为__dict__的字典中,该字典带有指向其他对象的"指针"。由于python中的函数也是对象,因此它们可以作为简单变量进行分配和操作,传递给其他函数等。python的对象定向实现利用了这一点,并将方法视为属性,将其视为对象的__dict__中的函数。

实例方法的第一个参数总是实例对象本身,通常称为self(但可以称为thisbanana)。当直接在class上调用一个方法时,它与任何实例都是不绑定的,因此必须给它一个实例对象作为第一个参数(A.func(A()))。调用绑定函数(A().func()时,方法的第一个参数self是隐式的,但在窗帘后面,python与直接调用未绑定函数并将实例对象作为第一个参数传递完全相同。

如果理解了这一点,那么分配A.func = func(窗帘后面的A.__dict__["func"] = func)给你留下了一个不受约束的方法这一事实并不令人吃惊。

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

classmethod接受第一个参数,获取实例的class对象,并将其作为第一个参数传递给函数调用,而staticmethod则简单地丢弃第一个参数并调用没有它的函数。


关于Mtsole对加布里埃尔·赫尔利回答的评论:

不同的是,func有一个__call__()方法,使其"可调用",即可以对其应用()操作符。查看python文档(在该页面上搜索__call__)。


点1:您定义的函数func在Python中作为第一类对象存在。

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

那么当您在python中传递一个函数作为class属性的值时会发生什么呢?该函数存储在类"EDOCX1"〔1〕中,通过调用分配给它的属性名,使它成为该类的一个方法。