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

时间:2010-02-21 22:11:43

标签: python class-method

>>> class A(object): pass
>>> def func(cls): pass
>>> A.func = func
>>> A.func
<unbound method A.func>

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

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

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

但更重要的是,如何将函数分配到一个类中?什么魔法可以解决这3件事?

更令人困惑的是:

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

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

5 个答案:

答案 0 :(得分:4)

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 Objects/funcobject.c中的具体功能为func_descr_get

答案 1 :(得分:4)

你说这与描述符协议有关是对的。描述符是如何传递接收器对象,因为方法的第一个参数是在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只返回原始函数。

答案 2 :(得分: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的面向对象实现利用了这一点,并将方法视为属性,作为{{1中的函数对象。

实例方法“first parameter is always the instance object itself,通常称为__dict__(但可以称为selfthis)。当直接在banana上调用方法时,它将被解除绑定到任何实例,因此您必须为其提供一个实例对象作为第一个参数(class)。当你调用bound functionA.func(A()))时,方法的第一个参数A().func()是隐含的,但是在幕后,Python与直接调用未绑定函数完全相同,将实例对象作为第一个参数传递。

如果理解了这一点,那么分配self(窗帘后面正在进行A.func = func)会给你留下一个未绑定的方法这一事实并不令人惊讶。

在您的示例中,A.__dict__["func"] = func中的cls实际上将传递的是def func(cls): pass类型的实例(self)。当您应用classmethodstaticmethod decorators时,除了在调用函数之前获取在函数/方法调用期间获得的第一个参数,并将其转换为其他参数之外,不做任何其他操作。

A获取第一个参数,获取实例的classmethod对象,并将其作为函数调用的第一个参数传递,而class只丢弃第一个参数并调用没有它的功能。

答案 3 :(得分:0)

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

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

那么当你在Python中传递函数作为类属性的值时会发生什么?该函数存储在类__dict__中,使其成为通过调用您为其指定的属性名访问该类的方法。

答案 4 :(得分:0)

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

不同之处在于func__call__()方法,使其“可调用”,即您可以将()运算符应用于此方法。查看the Python docs(在该页面上搜索__call__)。