Know if + or __add__ is called on an object

In Python, I can overload the __add__ method of an object (or other aka "dunder" double underscore methods). This allows me to define custom behavior for my objects when using Python statements.

Is it possible to find out from the dunder method if the method was called via + or via __add__ ?

For example, suppose I want to create an object that prints "+" or "__add__" , depending on whether + was used or if __add__ was called.

 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 

The inability of any magic such as method_how_created , is there canonical mapping of characters for dunder labels? I know that there are lists such as http://www.python-course.eu/python3_magic_methods.php or solutions based on the analysis of the docstrings of the operator module, as indicated here: Access operator functions by symbol . Is there a way to find a map between function names and characters that are slightly less hacked than parsing documents or manually creating a list?

+7
python
source share
2 answers

Yes, but you probably don't want to do this because it is a bad idea. You must check the interpreter stack. For obvious reasons, this will not work if your code is called from C. Here is the code:

 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) 

This is tested and works on Python 3 and requires only minor modifications to Python 2.

+5
source share

In cpython, you have the ability to introspect by disassembling the referrer frame. For example:

 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() 

Checking the case of x + 7 , you will see something like this when disassembling:

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

Using the magic x.__add__(7) method, you will see something like this when disassembling:

  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 
+4
source share

All Articles