[Python-Dev] Tightening up the specification for locals()

Steven D'Aprano steve at pearwood.info
Fri May 3 04:43:41 CEST 2013

On 03/05/13 11:29, Nick Coghlan wrote:
> An exchange in one of the enum threads prompted me to write down
> something I've occasionally thought about regarding locals(): it is
> currently severely underspecified, and I'd like to make the current
> CPython behaviour part of the language/library specification. (We
> recently found a bug in the interaction between the __prepare__ method
> and lexical closures that was indirectly related to this
> underspecification)

Fixing the underspecification is good. Enshrining a limitation as the
one correct way, not so good.

> * at function scope, locals() must return a *snapshot* of the current
> locals and free variables. Subsequent execution must not change the
> contents of the returned mapping and changes to the returned mapping
> must not change the execution environment.

If we were designing the language from scratch, with no concern for
optimizing function execution, would we want this as a language feature?
I don't believe that there is anyone who would say:

"I really want locals() to behave differently inside functions from how
it behaves inside classes and the global scope, as a feature in and of

Obviously CPython introduces that limitation for good reason, and I don't
wish to suggest that this is the wrong thing to do, but it is a trade-off,
and some implementations may wish to make other trade-offs, or even find
a way to avoid it altogether.

E.g. IronPython and Jython both allow this:

>>> def func():
...     x = 1; del x
...     locals()['x'] = 2
...     print x
>>> func()

And why not? In and of itself, writing to locals() inside a function is
no worse a thing to do than writing to locals() inside a class or global
scope. It's not something actively harmful that must be prohibited, so why
prohibit it?

I think that conforming Python implementations should be allowed a choice
between two fully-specified behaviours, the choice between them being a
"quality of implementation" issue:

- locals() may return a read-only or frozen mapping containing a snapshot
   of the current locals and free variable, in which case subsequent
   execution must not change the contents of the returned mapping, and
   changing the returned mapping is not possible;

- locals() may return an ordinary dict, in which case it must be the
   actual execution namespace, or a proxy to it. Subsequent execution
   will change the contents of the returned mapping, and changes to the
   mapping must change the execution environment.

Code can determine at runtime which capability is provided by inspecting
the type of the returned mapping: if isinstance(locals(), dict) then you
have support for modifying the executable environment, if not, you don't.

Obviously if you wish to write platform-agnostic code, you have to target
the least behaviour, which would be read-only locals. But there's lots of
code that runs only under Jython or IronPython, and if somebody really
needs to write to locals(), they can target an implementation that
provides that feature.


More information about the Python-Dev mailing list