我一直在阅读python中的魔术方法,我发现了很多关于覆盖它们以及它们服务的目的的信息,但我无法找到语言特定的操作符和操作映射到那些的位置方法(+
寻找__add__
,+=
寻找__iadd__
,从类中创建一个新对象可能会调用__new__
和__init__
等)是否有某处我可以看到当python解释器(或任何低层机制)遇到加号时会发生什么标志?
你的问题有点普遍。有一个全面的list“特殊方法”,即使它错过了一些stdlib特定方法(例如__setstate__
使用的__getstate__
和pickle
等。但它是pickle
模块的协议而不是语言协议)。
如果您想确切地知道解释器的作用,可以使用dis
模块来反汇编字节码:
>>> import dis
>>> def my_func(a):
... return a + 2
...
>>> dis.dis(my_func)
2 0 LOAD_FAST 0 (a)
3 LOAD_CONST 1 (2)
6 BINARY_ADD
7 RETURN_VALUE
您可以看到intereper在执行添加时执行BINARY_ADD
字节代码。如果你想查看BINARY_ADD
的确切操作,你可以下载Python的源代码并检查ceval.c
文件:
case BINARY_ADD:
w = POP();
v = TOP();
if (PyInt_CheckExact(v) && PyInt_CheckExact(w)) {
/* INLINE: int + int */
register long a, b, i;
a = PyInt_AS_LONG(v);
b = PyInt_AS_LONG(w);
/* cast to avoid undefined behaviour
on overflow */
i = (long)((unsigned long)a + b);
if ((i^a) < 0 && (i^b) < 0)
goto slow_add;
x = PyInt_FromLong(i);
}
else if (PyString_CheckExact(v) &&
PyString_CheckExact(w)) {
x = string_concatenate(v, w, f, next_instr);
/* string_concatenate consumed the ref to v */
goto skip_decref_vx;
}
else {
slow_add:
x = PyNumber_Add(v, w);
}
Py_DECREF(v);
skip_decref_vx:
Py_DECREF(w);
SET_TOP(x);
if (x != NULL) continue;
break;
所以在这里我们可以看到python特殊情况int和字符串添加,并最终回退到PyNumber_Add
,它检查第一个操作数是否实现__add__
并调用它,最终它尝试右侧的__radd__
,如果没有任何工作会引发TypeError
。
请注意,字节代码是特定于版本的,因此dis
将在不同版本上显示不同的结果:
# python2.7
>>> def my_func():
... return map((lambda x: x+1), range(5))
...
>>> dis.dis(my_func)
2 0 LOAD_GLOBAL 0 (map)
3 LOAD_CONST 1 (<code object <lambda> at 0x16f8c30, file "<stdin>", line 2>)
6 MAKE_FUNCTION 0
9 LOAD_GLOBAL 1 (range)
12 LOAD_CONST 2 (5)
15 CALL_FUNCTION 1
18 CALL_FUNCTION 2
21 RETURN_VALUE
# python3
>>> dis.dis(my_func)
2 0 LOAD_GLOBAL 0 (map)
3 LOAD_CONST 1 (<code object <lambda> at 0x7f1161a76930, file "<stdin>", line 2>)
6 LOAD_CONST 2 ('my_func.<locals>.<lambda>')
9 MAKE_FUNCTION 0
12 LOAD_GLOBAL 1 (range)
15 LOAD_CONST 3 (5)
18 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
21 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
24 RETURN_VALUE
同样的字节代码也可以在将来的版本中进行优化,因此即使字节代码相同,python的不同版本也会实际执行不同的指令。
如果您有兴趣了解python如何在幕后工作,我建议您编写一些C扩展,遵循官方python网站上可以找到的教程和文档。
由于所涉及的抽象级别,精确定位CPython源映射运算符+
到特殊方法__add__
的单个位置是非常重要的。
正如其他人回应的那样,+
是用BINARY_ADD
操作码实现的,后者调用PyNumber_Add
(除了在一些特别优化的情况下)。另一方面,PyNumber_Add
查看tp_as_number
member的type object以获得PyNumberMethods
结构,其nb_add
成员指向实现添加的C函数。
这对于define their own nb_add
直接内置类型来说很简单,但对Python中定义的__add__
来说有点复杂,需要将其转换为适当的nb_add
。这一部分由typeobject.c
处理:当你定义一个实现__add__
的类时,typeobject.c
installs中的机制进入object->type->tp_as_number->nb_add
这是一个泛型函数,它在对象上查找__add__
并调用它来实现添加。对于__add__
的情况,这个通用函数称为slot_nb_add
,使用defined是SLOT1BIN
macro。
至于__new__
和__init__
,它们被引用from the __call__
operator of the type
object本身(在CPython实现术语中的tp_call
)。这只是合乎逻辑的,因为在Python中您调用的是构造对象的类型。
dis
模块可以在某种程度上帮助您:
让我们举一个简单列表的例子:
In [12]: def func():
lis=[1,2,3]
for i in range(5):
lis+=[i]
....:
In [13]: def func1():
lis=[1,2,3]
for i in range(5):
lis =lis + [i]
....:
In [14]: dis.dis(func)
2 0 LOAD_CONST 1 (1)
3 LOAD_CONST 2 (2)
6 LOAD_CONST 3 (3)
#removed some lines of code
4 34 LOAD_FAST 0 (lis)
37 LOAD_FAST 1 (i)
40 BUILD_LIST 1
43 INPLACE_ADD # += means inplace add is used
# i.e `__iadd()__`
44 STORE_FAST 0 (lis)
47 JUMP_ABSOLUTE 28
>> 50 POP_BLOCK
>> 51 LOAD_CONST 0 (None)
54 RETURN_VALUE
In [15]: dis.dis(func1)
2 0 LOAD_CONST 1 (1)
3 LOAD_CONST 2 (2)
6 LOAD_CONST 3 (3)
9 BUILD_LIST 3
12 STORE_FAST 0 (lis)
#removed some lines of code
4 34 LOAD_FAST 0 (lis)
37 LOAD_FAST 1 (i)
40 BUILD_LIST 1
43 BINARY_ADD #normal binary add was used
#i.e __add__
44 STORE_FAST 0 (lis)
47 JUMP_ABSOLUTE 28
>> 50 POP_BLOCK
>> 51 LOAD_CONST 0 (None)
54 RETURN_VALUE
http://docs.python.org/2/library/dis.html
class x:
def __add__(self,other):
return "asd"
def test():
return x() + "aaaa"
import dis
dis.dis(test)
返回类似的东西
2 0 LOAD_GLOBAL 0 (x)
3 CALL_FUNCTION 0
6 LOAD_CONST 1 ('aaaa')
9 BINARY_ADD
10 RETURN_VALUE
那就是你最接近的“低级别”