Xoriun
Xoriun

Reputation: 169

use __getattr__ within the same module or console

I have setup a custom __getattr__() method on a module which works perfectly when called from outside the module. However, it doesn't work from within the module itself (since that uses the globals() dict which doesn't redirect to __getattr__ on failure). How would I work around that?

I was thinking about encapsulating the globals() into a custom dict where I can then modify the __getitem__ method, but that seems very dirty and error prone. Is there another way to do that?

MWE:

def __getattr__(name):
    f'custom {name}'

print(a)  # should print 'custom a', but raises NameError

Upvotes: 1

Views: 92

Answers (1)

blhsing
blhsing

Reputation: 107015

When a name is looked up in the global namespace, it is first looked up in the the globals() dict, and then the __builtins__ dict if that fails, so to customize the behavior of a missing name, you really want to customize the __builtins__ dict rather than the globals() dict.

But the __builtins__ dict of the current running frame can't be reassigned, so one workaround would be to re-execute the current frame's code object after customizing __builtins__ with a dict with a __missing__ method defined to delegate access to the module's __getattr__ function.

To avoid triggering the __builtins__ customization logics upon re-execution, you can check if __builtins__ is of the custom dict class before deciding whether to run the original intended code instead.

This approach works both as a main program and as an imported module:

if type(__builtins__).__name__ == '_DefaultDict':
    # your original code here
    def __getattr__(name):
        return f'custom {name}'

    print(a)
else:
    import sys
    import builtins

    class _DefaultDict(dict):
        def __missing__(self, name):
            return __getattr__(name)

    __builtins__ = _DefaultDict(vars(builtins))
    del _DefaultDict # avoid namespace pollution
    exec(sys._getframe(0).f_code, globals())

This outputs:

custom a

Demo: https://ideone.com/ThEpv6

Upvotes: 1

Related Questions