函数、非绑定方法和绑定方法之间有什么区别?
- 2025-01-17 09:23:00
- admin 原创
- 94
问题描述:
我之所以问这个问题,是因为这个答案的评论区有讨论。我已经理解了 90% 了。
In [1]: class A(object): # class named 'A'
...: def f1(self): pass
...:
In [2]: a = A() # an instance
f1
有三种不同的形式:
In [3]: a.f1 # a bound method
Out[3]: <bound method a.f1 of <__main__.A object at 0x039BE870>>
In [4]: A.f1 # an unbound method
Out[4]: <unbound method A.f1>
In [5]: a.__dict__['f1'] # doesn't exist
KeyError: 'f1'
In [6]: A.__dict__['f1'] # a function
Out[6]: <function __main__.f1>
f1 描述的绑定方法、非绑定方法和函数对象有什么区别?如何调用这三个对象?它们如何相互转化?这方面的文档很难理解。
解决方案 1:
函数由语句或创建。在 Python 2 下,当函数出现在语句主体中(或传递给类构造调用)时,它将转换为未绑定方法。(Python 3 没有未绑定方法;见下文。)当在类实例上访问函数时,它将转换为绑定方法,该方法会自动将实例作为第一个参数提供给方法。def
`lambdaclass
type`self
def f1(self):
pass
这f1
是一个函数。
class C(object):
f1 = f1
现在C.f1
是一种不受约束的方法。
>>> C.f1
<unbound method C.f1>
>>> C.f1.im_func is f1
True
我们还可以使用type
类构造函数:
>>> C2 = type('C2', (object,), {'f1': f1})
>>> C2.f1
<unbound method C2.f1>
我们可以f1
手动转换为非绑定方法:
>>> import types
>>> types.MethodType(f1, None, C)
<unbound method C.f1>
未绑定的方法受类实例上的访问约束:
>>> C().f1
<bound method C.f1 of <__main__.C object at 0x2abeecf87250>>
访问转化为通过描述符协议调用:
>>> C.f1.__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>
综合起来:
>>> types.MethodType(f1, None, C).__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf87310>>
或者直接:
>>> types.MethodType(f1, C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>
函数和未绑定方法之间的主要区别在于后者知道它绑定到哪个类;调用或绑定未绑定方法需要其类类型的实例:
>>> f1(None)
>>> C.f1(None)
TypeError: unbound method f1() must be called with C instance as first argument (got NoneType instance instead)
>>> class D(object): pass
>>> f1.__get__(D(), D)
<bound method D.f1 of <__main__.D object at 0x7f6c98cfe290>>
>>> C.f1.__get__(D(), D)
<unbound method C.f1>
由于函数和未绑定方法之间的区别非常小,Python 3消除了这种区别;在 Python 3 下,访问类实例上的函数只会给你函数本身:
>>> C.f1
<function f1 at 0x7fdd06c4cd40>
>>> C.f1 is f1
True
那么,在 Python 2 和 Python 3 中,这三个都是等效的:
f1(C())
C.f1(C())
C().f1()
将函数绑定到实例会将其第一个参数(通常称为self
)固定到实例。因此绑定方法C().f1
等效于以下任一操作:
(lamdba *args, **kwargs: f1(C(), *args, **kwargs))
functools.partial(f1, C())
解决方案 2:
很难理解
嗯,这是一个相当困难的话题,它与描述符有关。
让我们从函数开始。这里一切都很清楚 - 您只需调用它,执行时会传递所有提供的参数:
>>> f = A.__dict__['f1']
>>> f(1)
1
TypeError
如果参数数量出现任何问题,则会引发常规问题:
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f1() takes exactly 1 argument (0 given)
现在,方法。方法是带有一点趣味的函数。描述符在这里发挥作用。如数据模型中所述,A.f1
和分别A().f1
被翻译成A.__dict__['f1'].__get__(None, A)
和。这些的结果与原始函数不同。这些对象是原始对象的包装器,包含一些额外的逻辑。type(a).__dict__['f1'].__get__(a, type(a))
`__get__f1
f1`
在这种情况下,unbound method
逻辑包括检查第一个参数是否是以下实例A
:
>>> f = A.f1
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method f1() must be called with A instance as first argument (got nothing instead)
>>> f(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method f1() must be called with A instance as first argument (got int instance instead)
如果此检查成功,它将f1
以该实例作为第一个参数执行 original :
>>> f(A())
<__main__.A object at 0x800f238d0>
请注意,该im_self
属性是None
:
>>> f.im_self is None
True
如果出现bound method
这种逻辑,则会立即向 original 提供其创建f1
的实例(该实例实际上存储在属性中):A
`im_self`
>>> f = A().f1
>>> f.im_self
<__main__.A object at 0x800f23950>
>>> f()
<__main__.A object at 0x800f23950>
所以,bound
意味着底层函数与某个实例绑定。unbound
意味着它仍然是绑定的,但仅限于一个类。
解决方案 3:
函数对象是通过函数定义创建的可调用对象。绑定方法和非绑定方法都是通过点二元运算符调用的描述符创建的可调用对象。
绑定和非绑定方法对象有 3 个主要属性:im_func
是类中定义的函数对象、im_class
是类、im_self
是类实例。对于非绑定方法,im_self
是None
。
当调用绑定方法时,它会以im_func
它im_self
作为第一个参数,后面跟着它的调用参数。未绑定方法仅使用其调用参数来调用底层函数。
从 Python 3 开始,没有未绑定的方法。Class.method
返回对方法的直接引用。
解决方案 4:
请参阅Python 2和Python 3文档以了解更多详细信息。
我的解释如下。
课程Function
片段:
Python 3:
class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
if obj is None:
return self
return types.MethodType(self, obj)
Python 2:
class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
return types.MethodType(self, obj, objtype)
如果调用函数时没有使用类或实例,则它是一个普通函数。
如果从类或实例调用函数,
__get__
则会调用它来检索包装函数:
a.B.x
与 相同B.__dict__['x'].__get__(None, B)
。在 Python 3 中,这将返回普通函数。在 Python 2 中,这将返回未绑定的函数。
b.b.x
与 相同type(b).__dict__['x'].__get__(b, type(b)
。这将在 Python 2 和 Python 3 中返回一个绑定方法,这意味着self
将隐式传递为第一个参数。
解决方案 5:
函数、非绑定方法和绑定方法之间有什么区别?
从突破性的角度看,函数并没有什么区别。Python 面向对象特性是建立在基于函数的环境之上的。
受约束等于:
该函数是否将类(cls)或对象实例(self)作为第一个参数?
以下是示例:
class C:
#instance method
def m1(self, x):
print(f"Excellent m1 self {self} {x}")
@classmethod
def m2(cls, x):
print(f"Excellent m2 cls {cls} {x}")
@staticmethod
def m3(x):
print(f"Excellent m3 static {x}")
ci=C()
ci.m1(1)
ci.m2(2)
ci.m3(3)
print(ci.m1)
print(ci.m2)
print(ci.m3)
print(C.m1)
print(C.m2)
print(C.m3)
输出:
Excellent m1 self <__main__.C object at 0x000001AF40319160> 1
Excellent m2 cls <class '__main__.C'> 2
Excellent m3 static 3
<bound method C.m1 of <__main__.C object at 0x000001AF40319160>>
<bound method C.m2 of <class '__main__.C'>>
<function C.m3 at 0x000001AF4023CBF8>
<function C.m1 at 0x000001AF402FBB70>
<bound method C.m2 of <class '__main__.C'>>
<function C.m3 at 0x000001AF4023CBF8>
输出显示静态函数m3
永远不会被调用绑定。C.m2
被绑定到类是C
因为我们发送的cls
参数是类指针。
ci.m1
并且ci.m2
都是绑定的;ci.m1
因为我们发送的self
是指向实例的指针,并且ci.m2
因为实例知道该类是绑定的;)。
综上所述,您可以根据方法采用的第一个参数将方法绑定到类或类对象。如果方法未绑定,则可以将其称为未绑定。
请注意,该方法可能不是该类的原始部分。查看Alex Martelli 的此答案以了解更多详细信息。
解决方案 6:
今天我看到一件有趣的事情是,当我将一个函数分配给类成员时,它就变成了一个未绑定的方法。例如:
class Test(object):
@classmethod
def initialize_class(cls):
def print_string(self, str):
print(str)
# Here if I do print(print_string), I see a function
cls.print_proc = print_string
# Here if I do print(cls.print_proc), I see an unbound method; so if I
# get a Test object o, I can call o.print_proc("Hello")