知道是否在对象上调用了+或__add__

时间:2016-04-11 19:45:21

标签: python

在Python中,我可以重载对象的__add__方法(或其他双下划线称为“dunder”方法)。这允许我在使用Python运算符时为我的对象定义自定义行为。

是否可以从dunder方法中了解该方法是通过+还是通过__add__调用的?

例如,假设我想创建一个打印"+""__add__"的对象,具体取决于是否使用+或是否直接调用__add__

class MyAdder(object):
    def __add__(self, other):
        print method_how_created()
        return 0


MyAdder() + 7
# prints "+", returns 0

MyAdder().__add__(7)
# prints "__add__", returns 0

除了存在像method_how_created这样的魔法之外,是否存在符号到dunder方法的规范映射?我知道有一些列表,例如http://www.python-course.eu/python3_magic_methods.php或基于解析operator模块的文档字符串的解决方案,如下所述:Access operator functions by symbol。有没有办法在函数名和符号之间找到一个比解析文档字符串或手动创建列表更少hacky的地图?

2 个答案:

答案 0 :(得分:5)

是的,但你可能不想这样做,因为这是一个坏主意。您必须检查解释器堆栈。出于显而易见的原因,如果从C调用代码,这将不起作用。这是代码:

import inspect
import dis

class A(object):
    def __add__(self, other):
        fr = inspect.getouterframes(
            inspect.currentframe(), 1)[1][0]
        opcode = fr.f_code.co_code[fr.f_lasti]
        # opcode = ord(opcode) # Uncomment for Python 2
        is_op = opcode == dis.opmap['BINARY_ADD']
        if is_op:
            print('Called with +')
        else:
            print('Called with __add__')

A() + 1
A().__add__(1)

这是经过测试并适用于Python 3,并且只需要对Python 2稍作修改。

答案 1 :(得分:4)

在cpython中,你可以通过反汇编引用框架来获得一些内省能力。例如:

import dis
import inspect

def method_how_created():
    return dis.dis(inspect.currentframe().f_back.f_back.f_code)

class MyAdder(object):
    def __add__(self, other):
        print method_how_created()

x = MyAdder()

检查案例x + 7,您将在反汇编中看到类似的内容:

         64 LOAD_NAME                5 (x)
         67 LOAD_CONST               5 (7)
         70 BINARY_ADD          
         71 POP_TOP             
         72 LOAD_CONST               1 (None)
         75 RETURN_VALUE        

使用魔法x.__add__(7),您将在反汇编中看到类似的内容:

         64 LOAD_NAME                5 (x)
         67 LOAD_ATTR                6 (__add__)
         70 LOAD_CONST               5 (7)
         73 CALL_FUNCTION            1
         76 POP_TOP             
         77 LOAD_CONST               1 (None)
         80 RETURN_VALUE