>>> 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...>
但是当我检索属性时,我认为这与描述符有关。我不认为这与属性的设置有很大关系。
答案 0 :(得分:4)
Descriptors是神奇的 1 ,当你从一个实例或类中检索它时,它将普通函数转换为绑定或非绑定方法,因为它们只是需要不同的函数约束策略。 classmethod
和staticmethod
装饰器实现其他绑定策略,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.
因此,在类或实例上查找函数会将其转换为方法,而不是将其分配给类属性。
classmethod
和staticmethod
只是略有不同的描述符,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__
(但可以称为self
或this
)。当直接在banana
上调用方法时,它将被解除绑定到任何实例,因此您必须为其提供一个实例对象作为第一个参数(class
)。当你调用bound function(A.func(A())
)时,方法的第一个参数A().func()
是隐含的,但是在幕后,Python与直接调用未绑定函数完全相同,将实例对象作为第一个参数传递。
如果理解了这一点,那么分配self
(窗帘后面正在进行A.func = func
)会给你留下一个未绑定的方法这一事实并不令人惊讶。
在您的示例中,A.__dict__["func"] = func
中的cls
实际上将传递的是def func(cls): pass
类型的实例(self
)。当您应用classmethod
或staticmethod
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__
)。