Is there are any way make locals() and globals() defaultdict-like

480 Views Asked by At

Is it possible to change the behaviour of global and local variables in Python at runtime?

In Python, locals() gives references to variables in the current execution scope, which is a dict object.

>>> locals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, '__package__': None}

Is it possible to replace that reference returned by locals() to a defaultdict, but keep the previous values (a copy of locals()) before replacing it?

I would expect this to avoid UnboundLocalException exceptions when using uninitialized variables and access any variable name in the execution scope (uninitialized variables would take a specified default value).

I've tried to modify the value returned by locals() by reassigning it to locals without success.

The same question goes for globals().

2

There are 2 best solutions below

0
On BEST ANSWER

No, you can't. locals() is just a reflection of the actual namespace used for functions.

For performance reasons, the actual namespace is an array and locals are not looked up by name but by index. You can't add new names to this after the fact, because the compiler has simply not accounted for more references in the array.

Note that NameError exceptions are thrown for missing globals, not locals. Local names, if not yet bound, throw an UnboundLocalException instead. You can't replace the globals() dictionary with a defaultdict either, however; the __dict__ attribute of module objects is read-only. Even if it wasn't read-only, only the built-in dict type is supported due to the way names are looked up in the namespace; this is by design.

0
On

Yes, sort of, using metaclasses, Python's tool for modifying the behaviour of the class statement.

Here, DefaultDictMeta returns an instance of defaultdict from its __prepare__ method. Any class with a metaclass of DefaultDictMeta will therefore end up using an instance of defaultdict for its namespace. This means that name lookups can't fail in the body of NoNameErrorsInBody.

from collections import defaultdict

class DefaultDictMeta(type):
    def __prepare__(name, bases, default=None, **kwargs):
        return defaultdict(lambda: default)

    def __new__(meta, name, bases, dct, **kwargs):
        # swallow keyword arguments
        return super().__new__(meta, name, bases, dct)

    def __init__(cls, name, bases, dct, **kwargs):
        # swallow keyword arguments
        pass

class NoNameErrorsInBody(metaclass=DefaultDictMeta, default=2):
    x = y + 3  # y is not defined


>>> NoNameErrorsInBody.x
5

It goes without saying that you should never do this in real-world code. I'm only demonstrating it for a bit of fun. (Imagine trying to debug code which doesn't tell you when you use an invalid name. It'd be awful, like, I dunno, Javascript or something.)