In Python, I can overload an object's __add__
method (or other double-underscore aka "dunder" methods). This allows me to define custom behavior for my objects when using Python operators.
Is it possible to know, from within the dunder method, if the method was called via +
or via __add__
?
For example, suppose I want to create an object which prints "+"
or "__add__"
depending on if +
was used or if __add__
was called directly.
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
Barring the existence of some magic like method_how_created
, is there a canonical mapping of symbols to dunder methods? I know there are lists, such as http://www.python-course.eu/python3_magic_methods.php or solutions based on parsing the docstrings of the operator
module, as mentioned here: Access operator functions by symbol. Is there a way to find a map between function names and symbols that is a little less hacky than parsing the docstrings or manually creating a list?
Yes, but you probably don't want to do this, because it's a bad idea. You have to inspect the interpreter stack. For obvious reasons, this will not work if your code is called from C. Here's 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 slight modifications for Python 2.
In cpython, you have some introspection ability 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 x + 7
, you will see something like this in the disassembly:
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 method x.__add__(7)
, you will see something like this in the disassembly:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With