[Python-ideas] A "local" pseudo-function

Tim Peters tim.peters at gmail.com
Wed May 2 15:09:48 EDT 2018

>> There's another question that hasn't been asked yet: what should locals()
>> and globals() return?

[Tim, "globals()" is obvious, "locals()" can be surprising now]
> ...

And here recording the results of some code spelunking   Dicts don't
really have anything to do with how locals are implemented anymore;
calling "locals()" now inside a function _constructs_ a dict on the
fly out of lower-level implementation gimmicks, to be kinda-compatible
with what locals() returned before nested scopes were introduced.

The real distinctions of interest are recorded in the code object now,
under these attributes, where I'm giving a fuller explanation than can
be found in the docs, and where "referenced" doesn't distinguish
between "binding merely retrieved" and "binding is changed":

1. co_varnames:  tuple of names of locals not referenced in an
enclosed local scope

2. co_cellvars:  tuple of names of locals that are referenced in an
enclosed local scope

3 .co_freevars:  tuple of names local to an enclosing local scope and
referenced in this enclosed code

"locals()" now generally builds a dict out of all three of those name sources.

#1 is the only one that made sense before nested scopes were introduced.

The union of #1 and #2 is the set of names local to the code's scope;
CPython implements their bindings in different ways, so needs to
distinguish them.

The names in #3 aren't local to the code block at all (they are local
to some enclosing local scope), but for whatever reason are included
in the code's "locals()" dict anyway.  I would not have included them.

For concreteness:

    def disp(func):
        print("for", func.__name__)
        code = func.__code__
        for attr in "co_varnames", "co_cellvars", "co_freevars":
            print("   ", attr, getattr(code, attr))

    def outer():
        outer_only = 1
        outer_used_inner = 2
        outer_bound_by_inner = 3
        def inner():
            nonlocal outer_bound_by_inner
            inner_only = 4
            outer_bound_by_inner = 5
            inner_only = outer_used_inner

And its output:

for outer
    co_varnames ('outer_only', 'inner')
    co_cellvars ('outer_bound_by_inner', 'outer_used_inner')
    co_freevars ()
for inner
    co_varnames ('inner_only',)
    co_cellvars ()
    co_freevars ('outer_bound_by_inner', 'outer_used_inner')

More information about the Python-ideas mailing list