4

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?

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
lumisota
  • 101
  • 4
  • possible duplicate of [Why is it bad idea to modify locals in python?](http://stackoverflow.com/questions/4997184/why-is-it-bad-idea-to-modify-locals-in-python) – BartoszKP Mar 16 '15 at 18:05
  • @BartoszKP: none of the answers there are actually correct. – Martijn Pieters Mar 16 '15 at 18:06
  • @MartijnPieters The accepted answer says in essence the same what you say - that you shouldn't modify locals because it yields undefined behaviour. If you believe your answer is better perhaps you could consider posting it there, not here. I wouldn't have anything against the other question as a duplicate of this one if you insist. But seems you don't need voting for this :) – BartoszKP Mar 16 '15 at 18:09
  • @BartoszKP: There may be *better* dupes out there though. – Martijn Pieters Mar 16 '15 at 18:10
  • @MartijnPieters You're right. This one is the best I've found, but I admit I didn't spend more than 5 minutes on this :) – BartoszKP Mar 16 '15 at 18:11
  • "while change to locals() have no effect." If this was the case in 2015, it's not the case anymore. The locals() dictionary is in fact mutable and can be used to set local variables outside of the local scope. – Cerin Dec 03 '19 at 18:59

1 Answers1

7

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Why doesn't globals() behave in the same way? – lumisota Mar 16 '15 at 18:19
  • 2
    @lumisota: because globals is a) layered (it also reaches into builtins) and has to support altering at runtime (with the `global` keyword, as well as adding to the module namespace at runtime). – Martijn Pieters Mar 16 '15 at 18:20