I’ve discovered that if I write something like:
code = f"lambda a, b: add(a, b)"
my_globals = globals().copy()
my_globals['add'] = operator.__add__
result = eval(code, my_globals, {})
Then result(3, 5) has the value 8, just like I want.
When I look at the disassembly of result, I see:
0 LOAD_GLOBAL 0 (add)
2 LOAD_FAST 0 (a)
4 LOAD_FAST 1 (b)
6 CALL_FUNCTION 2
8 RETURN_VALUE
How does LOAD_GLOBAL convert add into operator.__add__? It clearly must save a reference to my_globals somewhere inside itself or its __code__ object.. But I’ve been unable to find it.
>Solution :
The reference you seek is the __globals__ attribute of the function object:
>>> result.__globals__
{'__name__': '__main__',
# Many lines omitted
'operator': <module 'operator' from '/usr/lib/python3.8/operator.py'>,
'code': 'lambda a, b: add(a, b)',
'add': <function _operator.add(a, b, /)>}
This attribute is documented in the Python Data model docs, under special attributes for user-defined functions:
__globals__:
A reference to the dictionary that holds the function’s global variables — the global namespace of the module in which the function was defined.
Read-only
As you found, the code object itself (__code__) does not contain a reference to the applicable globals dictionary. This is explicitly mentioned in the data model docs under "code objects":
Code objects represent byte-compiled executable Python code, or bytecode. The difference between a code object and a function object is that the function object contains an explicit reference to the function’s globals (the module in which it was defined), while a code object contains no context; also the default argument values are stored in the function object, not in the code object (because they represent values calculated at run-time). Unlike function objects, code objects are immutable and contain no references (directly or indirectly) to mutable objects.