Function locals are highly optimised and determined at compile time, CPython builds on not being able to alter the known locals dynamically at runtime.
You can see this when decoding a function bytecode:
>>> import dis
>>> def foo():
... a = 'bar'
... return a + 'baz'
...
>>> dis.dis(foo)
2 0 LOAD_CONST 1 ('bar')
3 STORE_FAST 0 (a)
3 6 LOAD_FAST 0 (a)
9 LOAD_CONST 2 ('baz')
12 BINARY_ADD
13 RETURN_VALUE
The LOAD_FAST
and STORE_FAST
opcodes use indices to load and store variables, because on a frame the locals is implemented as an array. Access to an array is faster than using a hash table (dictionary), such as used for the global namespace.
The locals()
function, when used in a function, then returns a reflection of this array as a dictionary. Altering the locals()
dictionary won't then reflect that back into the array.
In Python 2, if you use the exec
statement in your code then the optimisation is (partly) broken; Python uses the slower LOAD_NAME
opcode in that case:
>>> def bar(code):
... exec code
... return a + 'baz'
...
>>> dis.dis(bar)
2 0 LOAD_FAST 0 (code)
3 LOAD_CONST 0 (None)
6 DUP_TOP
7 EXEC_STMT
3 8 LOAD_NAME 0 (a)
11 LOAD_CONST 1 ('baz')
14 BINARY_ADD
15 RETURN_VALUE
Also see this bug report against Python 3 where exec()
(a function in Py3) doesn't allow you to set local names anymore:
To modify the locals of a function on the fly is not
possible without several consequences: normally, function locals are not
stored in a dictionary, but an array, whose indices are determined at
compile time from the known locales. This collides at least with new
locals added by exec. The old exec statement circumvented this, because
the compiler knew that if an exec without globals/locals args occurred
in a function, that namespace would be "unoptimized", i.e. not using the
locals array. Since exec() is now a normal function, the compiler does
not know what "exec" may be bound to, and therefore can not treat is
specially.