11

我一直在阅读有关 python 中的魔术方法的信息,并且我发现了很多关于覆盖它们以及它们服务的目的的信息,但是我无法找到在语言中特定的运算符和操作映射到那些的位置方法(+查找__add__+=查找__iadd__、从类中创建新对象可能会调用__new____init__等)是否有什么地方可以看到当 python 解释器(或任何较低级别的机制)遇到加号时会发生什么?

4

5 回答 5

5

你的问题有点笼统。有一个完整的“特殊方法”列表,即使它错过了一些 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   

您可以看到 intereperBINARY_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 和 string 添加,并最终回退到PyNumber_Add,它检查第一个操作数是否实现__add__并调用它,最终它尝试__radd__右侧,如果没有任何效果则引发 a 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 网站上找到的教程和文档。

于 2012-11-11T18:56:33.770 回答
4

由于涉及的抽象级别,将 CPython 源映射运算符中的单个位置精确定位+到特殊方法并非易事。__add__

正如其他人回应的那样,+是用BINARY_ADD操作码实现的,它调用PyNumber_Add(在一些特别优化的情况下除外)。PyNumber_Add另一方面,查看类型对象的tp_as_number成员以获取其成员指向实现加法的 C 函数的结构。PyNumberMethodsnb_add

这对于直接定义 自己的 内置类型来说很简单nb_add,但对于在 Python 中定义的类型来说有点复杂__add__,需要将其转换为适当的nb_add. 这部分由以下方式处理typeobject.c:当您定义一个实现 的类时__add__,其中的机器typeobject.c 安装object->type->tp_as_number->nb_add一个通用函数中,该函数查找__add__对象并调用它来实现加法。对于 的情况__add__,这个通用函数被调用slot_nb_add并使用定义SLOT1BIN

至于__new__and __init__,它们是从对象__call__type本身的运算符tp_call中调用的(用 CPython 实现的术语)。这只是合乎逻辑的,因为在 Python 中,您正在调用类型来构造对象。

于 2012-11-11T20:28:37.923 回答
3

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        
于 2012-11-11T18:51:29.593 回答
2

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

那是你最接近“低水平”的地方

于 2012-11-11T18:50:35.673 回答
1

您可能需要查看文档的这一部分:

http://docs.python.org/3/reference/datamodel.html#special-method-names

于 2012-11-11T19:00:14.000 回答