PEP 667 has this example (slightly modified):
def l():
"Get the locals of caller"
return sys._getframe(1).f_locals
def test():
if 0: y = 1 # Make 'y' a local variable
x = 1
l()['x'] = 2
l()['y'] = 4
l()['z'] = 5
print(locals(), x)
print(y)
print(z)
test() will print
{'x': 2, 'y': 4, 'z': 5} 2
4
5
I see that x and y are local variables, but z is only there becuse it was inserted in l().
Under Python 3.9, the three print()s compile as:
LOAD_GLOBAL 1 (print)
LOAD_GLOBAL 2 (locals)
CALL_FUNCTION 0
LOAD_FAST 0 (x)
CALL_FUNCTION 2
LOAD_GLOBAL 1 (print)
LOAD_FAST 1 (y)
CALL_FUNCTION 2
LOAD_GLOBAL 1 (print)
LOAD_GLOBAL 3 (z)
CALL_FUNCTION 2
And under Python 3.9, calling test() prints {'x': 1, 'z': 5} 1. Either of the other two print()s raise an error, because y is unbound and z is undefined.
If z is a free variable captured from an enclosing function, then the LOAD_GLOBAL would be LOAD_DEREF instead.
My questions are:
- What gets compiled for accessing these three variables? I’m hoping that
xandyuseLOAD_FASTandzusesLOAD_NAME - Will the accesses to
xandystill be fast? If not, then this is a big performance hit, defeating the wholeLOAD_NAMEoptimization.
= What happens with accessingz? It’s clear to me thatlocals()will be in sync withf_locals, and both will containz : 5. But does this also carry over toprint(z)? Or willzbe a global variable?
Suggestions - Make sure that
LOAD_FASTis still compiled forxandy. - I suggest that the PEP make the answer clear, regarding
z. - And perhaps you might consider compiling with
LOAD_NAMEforzinstead ofLOAD_GLOBALorLOAD_DEREF. This would be slightly slower by checkingf_localsfirst, but it would mean that updates tolocals()would show up as local variables.
You could optimize this by making a note of whether f_locals has ever been updated for a name not infastlocals, and if not, it would not check f_locals (knowing thatLOAD_NAMEis never called whenLOAD_FASTwould do).