函数、未绑定方法和绑定方法之间的区别是什么?

我问这个问题是因为在 这个答案的评论帖子上有一个讨论。我已经有九成的把握了。

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描述的 约束法不受约束的方法功能对象之间的区别是什么?如何调用这三个对象?它们如何互相转化?这东西上的 文件很难理解。

36115 次浏览

功能def语句或 lambda创建。在 Python 2中,当一个函数出现在 class语句体中(或者传递给 type类构造调用)时,它就被转换为 不受约束的方法。(Python3没有未绑定的方法; 见下文)当在类实例上访问一个函数时,它被转换成一个 约束法,该 约束法自动将该实例作为第一个 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

因此,在 Python2和 Python3中,这三个是等价的:

f1(C())
C.f1(C())
C().f1()

将函数绑定到实例的效果是将其第一个参数(通常称为 self)固定到实例。因此,绑定方法 C().f1等价于以下任何一种:

(lamdba *args, **kwargs: f1(C(), *args, **kwargs))
functools.partial(f1, C())

很难理解

嗯,这是一个相当难的主题,它必须与描述符有关。

让我们从函数开始。这里一切都很清楚——只要调用它,所有提供的参数都在执行时传递:

>>> 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.f1A().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,并将该实例作为第一个参数:

>>> f(A())
<__main__.A object at 0x800f238d0>

注意,im_self属性是 None:

>>> f.im_self is None
True

对于 bound method,这个逻辑立即为原来的 f1提供一个 A的实例(这个实例实际上存储在 im_self属性中) :

>>> f = A().f1
>>> f.im_self
<__main__.A object at 0x800f23950>
>>> f()
<__main__.A object at 0x800f23950>

因此,bound意味着底层函数绑定到某个实例。unbound意味着它仍然是绑定的,但是只绑定到一个类。

函数对象是由函数定义创建的可调用对象。绑定和未绑定方法都是可调用对象,由点二进制运算符调用的 Descriptor 创建。

绑定和未绑定的方法对象有3个主要属性: im_func是类中定义的函数对象,im_class是类,im_self是类实例。对于未绑定的方法,im_selfNone

当调用绑定方法时,它将以 im_self作为第一个参数调用 im_func,然后调用其参数。未绑定的方法只调用基础函数的调用参数。

从 Python3开始,没有未绑定的方法。 Class.method返回对该方法的直接引用。

今天我看到的一个有趣的事情是,当我将一个函数分配给一个类成员时,它就变成了一个未绑定的方法。例如:

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")

有关详细信息,请参阅 巨蟒2巨蟒3文档。

我的解释如下。

类别 Function片段:

巨蟒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)

巨蟒2:

class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
return types.MethodType(self, obj, objtype)
  1. 如果调用的函数没有类或实例,那么它就是一个普通函数。
  2. 如果从类或实例调用函数,则调用其 __get__来检索包装函数:
    B.xB.__dict__['x'].__get__(None, B)相同。 在 Python3中,这将返回普通函数。 在 Python2中,这将返回一个未绑定的函数。

    b.xtype(b).__dict__['x'].__get__(b, type(b)相同。这将返回 Python2和 Python3中的绑定方法,这意味着 self将作为第一个参数隐式传递。

函数、未绑定方法和绑定方法之间的区别是什么?

从开创性的 什么是函数角度来看,没有什么不同。 Python 面向对象特性是在基于函数的环境上构建的。

被约束 等于:

这个函数是以 同学们(Cls)还是 对象实例(自我)作为第一个参数呢?

下面是一个例子:

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.m1ci.m2都是绑定的; ci.m1是因为我们发送了指向实例的 self,而 ci.m2是因为实例知道类是绑定的;)。

为了得出结论,您可以根据方法采用的第一个参数将方法绑定到类或类对象。如果方法未绑定,则可以将其调用为未绑定。


请注意,该方法可能不是最初的类的一部分。检查 这个答案从亚历克斯马特利的更多细节。