Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

globals() vs locals() mutability

In Python, globals() returns a representation of the global symbol table, while locals() returns a representation of the local state. While both return a dictionary, changes to globals() are effected in the global symbol table, while change to locals() have no effect.

Why is this the case?

like image 407
lumisota Avatar asked Mar 16 '23 17:03

lumisota


1 Answers

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.

like image 127
Martijn Pieters Avatar answered Mar 27 '23 23:03

Martijn Pieters