Python 字节码函数调用传递自

Python bytecode function call passing self

我正在尝试了解字节码的工作原理。

a.func() 是一个函数调用。对应的字节码大致是LOAD_GLOBAL aLOAD_ATTR attr然后是CALL_FUNCTION0个参数。

如果 a 是一个模块,这完全没问题。但是如果 a 是一个对象,它必须传递对象实例本身。由于 Python 在编译时无法知道 a 是模块还是对象,因此无论 a 的类型如何,字节码自然都是相同的。但是如果 a 是一个对象,运行时系统如何处理 self 作为 func 的第一个参数呢?字节码级别以下是否有一些特殊处理,表示 "if it is called on an object prepend the object as the first argument"?

LOAD_ATTR 通过描述符(https://docs.python.org/2/howto/descriptor.html)施展魔法。

假设 a 是 class A 的对象: 在 python 中函数是描述符。当您执行 a.func 时,实际上它是 returns A.func,这是描述符对象(未绑定函数)。然后它 "upgrades" 本身绑定函数(调用 A.func.__get__ )。未绑定的函数必须首先显式地给出 self 参数。绑定函数本身已经记住了 self 参数 "inside"。

在python中模块是一个对象并且使用完全相同的机制。

简而言之,a.func 已经知道它绑定到哪个对象,因此不需要明确的 self(它已经知道 self 是什么):

>>> a.func
<bound method A.func of <__main__.A object at 0x10e810a10>>

将此与 A.func 对比(其中 A 是 class):

>>> A.func
<unbound method A.func>

调用 A.func 确实需要显式 self:

>>> A.func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method func() must be called with A instance as first argument (got nothing instead)
>>> A.func(a)
>>>

或者,在字节码中:

          0 LOAD_GLOBAL              0 (A)
          3 LOAD_ATTR                1 (func)
          6 LOAD_GLOBAL              2 (a)
          9 CALL_FUNCTION            1
         12 POP_TOP             

(注意多余的LOAD_GLOBAL。)

Python Language Reference 中解释了绑定与未绑定方法的机制(搜索 im_self__self__)。

字节码不必因不同的对象类型而不同。管理 绑定行为 是对象类型本身的责任。 descriptor protocol.

中对此进行了介绍

简而言之,LOAD_ATTR 通过 object.__getattribute__ hook:

委托对对象的属性访问

Called unconditionally to implement attribute accesses for instances of the class.

对于模块,__getattribute__ 只需在 __dict__ 命名空间中查找名称并 return 即可。但是对于 类 和 meta类,如果属性支持,实现将调用描述符协议。函数支持描述符协议和 return 一个 绑定方法 当被问到时:

>>> class Foo:
...     def method(self): pass
...
>>> Foo().method  # access on an instance -> binding behaviour
<bound method Foo.method of <__main__.Foo object at 0x107155828>>
>>> Foo.method    # access on the class, functions just return self when bound here
<function Foo.method at 0x1073702f0>
>>> Foo.method.__get__(Foo(), Foo)  # manually bind the function
<bound method Foo.method of <__main__.Foo object at 0x107166da0>>

这种绑定行为也是 property, classmethod and staticmethod 对象如何工作的基础(后者通过 return 函数本身消除了函数的绑定行为)。