关于python:函数、未绑定方法和绑定方法有什么区别?

What is the difference between a function, an unbound method and a bound method?

我问这个问题是因为对这个答案的评论线索进行了讨论。我有90%的方法可以让我的头脑清醒过来。

1
2
3
4
In [1]: class A(object):  # class named 'A'
   ...:     def f1(self): pass
   ...:
In [2]: a = A()  # an instance

f1有三种不同的形式:

1
2
3
4
5
6
7
8
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描述?如何称呼这三个物体?他们怎么能互相转化呢?关于这些东西的文档很难理解。


函数是由def语句或lambda语句创建的。在python2中,当函数出现在class语句的主体中(或传递给type类构造调用)时,它将转换为未绑定的方法。(python 3没有未绑定的方法;请参见下文)当在类实例上访问函数时,它将转换为绑定的方法,该方法自动将实例提供给方法作为第一个self参数。

1
2
def f1(self):
    pass

这里,f1是一个函数。

1
2
class C(object):
    f1 = f1

现在,C.f1是一种未绑定的方法。

1
2
3
4
>>> C.f1
<unbound method C.f1>
>>> C.f1.im_func is f1
True

我们还可以使用type类构造函数:

1
2
3
>>> C2 = type('C2', (object,), {'f1': f1})
>>> C2.f1
<unbound method C2.f1>

我们可以手动将f1转换为未绑定方法:

1
2
3
>>> import types
>>> types.MethodType(f1, None, C)
<unbound method C.f1>

未绑定的方法由类实例上的访问绑定:

1
2
>>> C().f1
<bound method C.f1 of <__main__.C object at 0x2abeecf87250>>

访问被转换为通过描述符协议调用:

1
2
>>> C.f1.__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>

结合这些:

1
2
>>> types.MethodType(f1, None, C).__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf87310>>

或直接:

1
2
>>> types.MethodType(f1, C(), C)                
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>

函数和未绑定方法的主要区别在于,后者知道它绑定到哪个类;调用或绑定未绑定方法需要其类类型的实例:

1
2
3
4
5
6
7
8
>>> 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>

由于函数和未绑定方法之间的差异非常小,所以python3就不需要区分了;在python3中,访问类实例上的函数只需提供函数本身:

1
2
3
4
>>> C.f1
<function f1 at 0x7fdd06c4cd40>
>>> C.f1 is f1
True

在python2和python3中,这三个是等效的:

1
2
3
f1(C())
C.f1(C())
C().f1()

将一个函数绑定到一个实例具有将其第一个参数(通常称为self)固定到该实例的效果。因此,绑定方法C().f1相当于:

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


is quite hard to understand

好吧,这是一个很难的主题,它与描述符有关。

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

1
2
3
>>> f = A.__dict__['f1']
>>> f(1)
1

当参数个数有问题时,提出常规TypeError

1
2
3
4
>>> 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的实例:

1
2
3
4
5
6
7
8
9
>>> 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

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

注意,im_self属性为None

1
2
>>> f.im_self is None
True

bound method的情况下,此逻辑立即向原始f1提供创建它的A的一个实例(此实例实际上存储在im_self属性中):

1
2
3
4
5
>>> f = A().f1
>>> f.im_self
<__main__.A object at 0x800f23950>
>>> f()
<__main__.A object at 0x800f23950>

因此,bound意味着基础函数与某些实例绑定。unbound表示它仍然是绑定的,但只限于一个类。


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

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

当调用绑定方法时,它调用im_func,其中im_self是第一个参数,紧随其调用参数。未绑定方法仅使用其调用参数调用基础函数。


有关详细信息,请参阅python 2和python 3文档。

我的解释如下。

Function代码段:

Python 3:

1
2
3
4
5
6
7
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:

1
2
3
4
5
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.xB.__dict__['x'].__get__(None, B)相同。在Python3中,这将返回纯函数。在Python2中,这将返回一个未绑定的函数。

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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")