My original question settles down to, 'just what is locals()
?'
Here's my current (speculative) understanding, written in Pythonese:
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
What is the nature of Python's locals() ?
Every local namespace has it's own namespace-table, which can be
viewed in total using the locals
built-in function.
(A namepace-table, in effect, is like a dict that holds "identifier": object entries; for each item, the key is the name (in string-form) assigned (or 'bound') to the object.)
When called in a non-global level, locals
returns the interpreter's sole representation of the current local namespace-table: a 'dynamic', always-up-to-date, specialized, dict-like object.
It's not a simple dict, nor is it the actual name-table, but it's effectively 'alive', and is instantly updated from the live table anytime it is referenced (when tracing is on, it updates with every statement).
On exiting the scope, this object vanishes, and is created anew for the current scope whenever locals
is next called.
(When called in a global (modular) level, locals
instead returns globals()
, Python's global-namespace representation, which may have a different nature).
So, L = locals()
binds the name L
to this 'stand-in' for the local namespace-table; subsequently, anytime L
is referenced, this object is refreshed and returned.
And, any additional names bound (in the same scope) to locals()
will be aliases for this same object.
Note that L
, being assigned to locals()
, necessarily becomes an 'infinite recursion' object (displayed as the dict {...}
), which may or may not be important to you. You can make a simple dict copy of L
at any time, however.
Some attributes of locals()
, such as keys
, also return simple objects.
To capture a 'pristine' snapshot of locals() within a function, use a technique that doesn't make any local assignments; for example, pass the copy as argument to a function, that pickles it out to a file.
There are particulars about L
, and how it behaves; it includes free variables from the blocks of functions, but not classes, and, the docs warn against trying to alter L
's contents (it may no longer 'mirror' the name-table).
It perhaps should only be read (copied, etc.)
(Why locals()
is designed to be 'live', rather than a 'snapshot', is another topic).
In summary:
locals()
is a unique, specialized object (in dict-form); it's Python's live representation of the current local namespace-table (not a frozen snapshot)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A way to get the results I had expected, then, is to produce copies of locals()
(here using dict.copy), at each step:
# A function copies locals() several times, and returns each result ...
def func():
var = 'var!'
locals_1 = locals().copy()
locals_2 = locals().copy()
locals_3 = locals().copy()
return locals_1, locals_2, locals_3
func is called, and the returns are displayed:
locals_1: {'var': 'var!'}
locals_2: {'var': 'var!', 'locals_1': {'var': 'var!'}}
locals_3: {'var': 'var!', 'locals_1': {'var': 'var!'}, 'locals_2':{'var':'var!','locals_1': {'var': 'var!'}}}
The returns are simple dict objects, that capture the growing stages of the local namespace.
This is what I intended.
Other possible ways to copy locals()
(here "L") are dict(L)
, copy.copy(L)
and copy.deepcopy(L)
.