148

How can implement the equivalent of a __getattr__ on a class, on a module?

Example

When calling a function that does not exist in a module's statically defined attributes, I wish to create an instance of a class in that module, and invoke the method on it with the same name as failed in the attribute lookup on the module.

class A(object):
    def salutation(self, accusative):
        print "hello", accusative

# note this function is intentionally on the module, and not the class above
def __getattr__(mod, name):
    return getattr(A(), name)

if __name__ == "__main__":
    # i hope here to have my __getattr__ function above invoked, since
    # salutation does not exist in the current namespace
    salutation("world")

Which gives:

matt@stanley:~/Desktop$ python getattrmod.py 
Traceback (most recent call last):
  File "getattrmod.py", line 9, in <module>
    salutation("world")
NameError: name 'salutation' is not defined
Matt Joiner
  • 112,946
  • 110
  • 377
  • 526
  • 1
    You aren't in your case even making an attribute access, so you are asking for two different things at once. So the major question is which one you want. Do you want `salutation` to exist in the global or local namespace (which is what the code above is trying to do) or do you want dynamic lookup of names when you make a dot access on a module? It's two different things. – Lennart Regebro Oct 06 '11 at 07:40
  • Interesting question, how did you come up with this? – Chris Wesseling Oct 11 '11 at 22:57
  • One of Python's mantras is "explicit is better than implicit". I think S.Lott's solution is the best under that reasoning. – Nick Johnson Oct 12 '11 at 05:09
  • 1
    possible duplicate of [Autoload in Python](http://stackoverflow.com/questions/1024455/autoload-in-python) – Piotr Dobrogost Sep 30 '12 at 10:05
  • may be used to deprecate module level globals https://stackoverflow.com/questions/922550/how-to-mark-a-global-as-deprecated-in-python/922693#922693 – n611x007 Dec 17 '14 at 12:39
  • 3
    `__getattr__` on modules is supported from Python 3.7 – fumito Jan 04 '20 at 04:08
  • This works in py37 `getattr(your_module,'YourClass')` and `getattr(your_module,'your_function')` – citynorman Oct 02 '20 at 03:45

8 Answers8

135

There are two basic problems you are running into here:

  1. __xxx__ methods are only looked up on the class
  2. TypeError: can't set attributes of built-in/extension type 'module'

(1) means any solution would have to also keep track of which module was being examined, otherwise every module would then have the instance-substitution behavior; and (2) means that (1) isn't even possible... at least not directly.

Fortunately, sys.modules is not picky about what goes there so a wrapper will work, but only for module access (i.e. import somemodule; somemodule.salutation('world'); for same-module access you pretty much have to yank the methods from the substitution class and add them to globals() eiher with a custom method on the class (I like using .export()) or with a generic function (such as those already listed as answers). One thing to keep in mind: if the wrapper is creating a new instance each time, and the globals solution is not, you end up with subtly different behavior. Oh, and you don't get to use both at the same time -- it's one or the other.


Update

From Guido van Rossum:

There is actually a hack that is occasionally used and recommended: a module can define a class with the desired functionality, and then at the end, replace itself in sys.modules with an instance of that class (or with the class, if you insist, but that's generally less useful). E.g.:

# module foo.py

import sys

class Foo:
    def funct1(self, <args>): <code>
    def funct2(self, <args>): <code>

sys.modules[__name__] = Foo()

This works because the import machinery is actively enabling this hack, and as its final step pulls the actual module out of sys.modules, after loading it. (This is no accident. The hack was proposed long ago and we decided we liked enough to support it in the import machinery.)

So the established way to accomplish what you want is to create a single class in your module, and as the last act of the module replace sys.modules[__name__] with an instance of your class -- and now you can play with __getattr__/__setattr__/__getattribute__ as needed.


Note 1: If you use this functionality then anything else in the module, such as globals, other functions, etc., will be lost when the sys.modules assignment is made -- so make sure everything needed is inside the replacement class.

Note 2: To support from module import * you must have __all__ defined in the class; for example:

class Foo:
    def funct1(self, <args>): <code>
    def funct2(self, <args>): <code>
    __all__ = list(set(vars().keys()) - {'__module__', '__qualname__'})

Depending on your Python version, there may be other names to omit from __all__. The set() can be omitted if Python 2 compatibility is not needed.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • 5
    *This works because the import machinery is actively enabling this hack, and as its final step pulls the actual module out of sys.modules, after loading it* Is it mentioned somewhere in the docs? – Piotr Dobrogost Sep 30 '12 at 15:08
  • 5
    Now I feel more at ease using this hack, considering it "semi-sanctioned" :) – Mark Nunberg Jan 11 '13 at 18:04
  • 3
    This is doing screwy things, like making `import sys` give `None` for `sys`. I'm guessing this hack isn't sanctioned in Python 2. – asmeurer Jun 06 '13 at 22:47
  • 1
    @asmeurer: This hack was created in Python 2. You must be doing something wrong. ;) – Ethan Furman Jun 07 '13 at 01:41
  • 3
    @asmeurer: To understand the reason for that (and a solution) see the question [Why is the value of \_\_name__ changing after assignment to sys.modules\[\_\_name__\]?](http://stackoverflow.com/questions/5365562/why-is-the-value-of-name-changing-after-assignment-to-sys-modules-name). – martineau Feb 21 '14 at 05:05
  • 1
    @martineau: Interesting! Now we know why reassigning `sys.modules[__name__]` should be the *last* thing one does! ;) – Ethan Furman Feb 21 '14 at 10:01
  • Is this still the recommended way many years later? Namely is it still the right hack in Python3.5? – Dima Tisnek Nov 27 '15 at 09:49
  • 1
    @qarma: I seem to recall some enhancements being talked about that would allow python modules to more directly participate in the class inheritance model, but even so this method still works and is supported. – Ethan Furman Nov 28 '15 at 16:41
  • As written here, it does *not* work properly, except for the most trivial toy examples which don't need to use any global/closure/outer names. Otherwise, you'll need to defeat the GC, for example using the reference count hack as shown in mouad's answer [here](https://stackoverflow.com/a/5365733/674039). – wim Dec 28 '18 at 19:51
  • @wim: If one is using this advanced functionality, one should be able to put all needed globals/closures/outer names inside the class that will be replacing the module. – Ethan Furman Dec 29 '18 at 09:53
  • Q: It works for `import foo`. But is there a way to get it working also for `from foo import *`? – Friedrich -- Слава Україні May 05 '20 at 07:01
  • 2
    @Friedrich: Yes. Check **Note 2** in my updated answer. – Ethan Furman May 05 '20 at 14:56
  • @Ethan Furman: Thanks. I'm deeply impressed by this hack. For python3, it results in `TypeError: 'dict_keys' object does not support indexing`. To get it working for python 2 and 3, I use `__all__ = list(set(vars().keys()) - {'__qualname__'})`. – Friedrich -- Слава Україні May 05 '20 at 16:31
  • @Friedrich: Thanks -- I had tested with Python 2. – Ethan Furman May 05 '20 at 20:17
  • @EthanFurman: On Python 3.4+, I think it is better to also set the `__spec__` attribute of the class (e.g., add the line `__spec__ = __spec__` to the class definition). – guy szweigman Apr 22 '23 at 13:31
74

A while ago, Guido declared that all special method lookups on new-style classes bypass __getattr__ and __getattribute__. Dunder methods had previously worked on modules - you could, for example, use a module as a context manager simply by defining __enter__ and __exit__, before those tricks broke.

Recently some historical features have made a comeback, the module __getattr__ among them, and so the existing hack (a module replacing itself with a class in sys.modules at import time) should be no longer necessary.

In Python 3.7+, you just use the one obvious way. To customize attribute access on a module, define a __getattr__ function at the module level which should accept one argument (name of attribute), and return the computed value or raise an AttributeError:

# my_module.py

def __getattr__(name: str) -> Any:
    ...

This will also allow hooks into "from" imports, i.e. you can return dynamically generated objects for statements such as from my_module import whatever.

On a related note, along with the module getattr you may also define a __dir__ function at module level to respond to dir(my_module). See PEP 562 for details.

wim
  • 338,267
  • 99
  • 616
  • 750
  • 1
    If I dynamically create a module via `m = ModuleType("mod")` and set `m.__getattr__ = lambda attr: return "foo"`; however, when I run `from mod import foo`, I get `TypeError: 'module' object is not iterable`. – weberc2 Jan 14 '20 at 22:49
  • @weberc2: Make that `m.__getattr__ = lambda attr: "foo"`, plus you need to define an entry for the module with `sys.modules['mod'] = m`. Afterward, there's no error with `from mod import foo`. – martineau Feb 14 '20 at 12:36
  • wim: You can also get dynamically computed values—like having a module-level property—allowing one to write `my_module.whatever` to invoke it (after an `import my_module`). – martineau Feb 14 '20 at 12:58
  • Regarding Cython support for PEP 562: defining a module function `__getattr__` and using it to access `globals()`, as described in the first example of PEP 562, appears to work as expected. Declaring public or read-only variables is not yet possible in a Cythonic way from what I understand, see: https://github.com/cython/cython/issues/656 and https://github.com/cython/cython/issues/3959, and also: https://cython.readthedocs.io/en/latest/src/changes.html (Section 0.29.22, Subsection "Other changes"). – 0 _ Apr 13 '21 at 08:37
51

This is a hack, but you can wrap the module with a class:

class Wrapper(object):
  def __init__(self, wrapped):
    self.wrapped = wrapped
  def __getattr__(self, name):
    # Perform custom logic here
    try:
      return getattr(self.wrapped, name)
    except AttributeError:
      return 'default' # Some sensible default

sys.modules[__name__] = Wrapper(sys.modules[__name__])
Håvard S
  • 23,244
  • 8
  • 61
  • 72
  • That may work but it's probably not a solution to the real problem of the author. – DasIch Mar 15 '10 at 22:15
  • 14
    "May work" and "probably not" isn't very helpful. It's a hack/trick, but it works, and solves the problem posed by the question. – Håvard S Mar 16 '10 at 07:41
  • 6
    While this will work in _other_ modules that import your module and access nonexistent attributes on it, it won't work for the actual code example here. Accessing globals() does not go through sys.modules. – Marius Gedminas May 03 '10 at 15:31
  • 5
    Unfortunately this doesn't work for the current module, or likely for stuff accessed after an `import *`. – Matt Joiner May 06 '10 at 02:25
21

We don't usually do it that way.

What we do is this.

class A(object):
....

# The implicit global instance
a= A()

def salutation( *arg, **kw ):
    a.salutation( *arg, **kw )

Why? So that the implicit global instance is visible.

For examples, look at the random module, which creates an implicit global instance to slightly simplify the use cases where you want a "simple" random number generator.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • If you're *really* ambitious, you could create the class, and iterate through all its methods and create a module-level function for each method. – Paul Fisher Mar 15 '10 at 13:32
  • @Paul Fisher: Per the problem, the class already exists. Exposing all methods of the class might not be a good idea. Usually these exposed methods are "convenience" methods. Not all are appropriate for the implicit global instance. – S.Lott Mar 15 '10 at 13:53
13

Similar to what @Håvard S proposed, in a case where I needed to implement some magic on a module (like __getattr__), I would define a new class that inherits from types.ModuleType and put that in sys.modules (probably replacing the module where my custom ModuleType was defined).

See the main __init__.py file of Werkzeug for a fairly robust implementation of this.

SuperShoot
  • 9,880
  • 2
  • 38
  • 55
Matt Anderson
  • 19,311
  • 11
  • 41
  • 57
12

This is hackish, but...

# Python 2.7
import types


class A(object):
    def salutation(self, accusative):
        print("hello", accusative)
    def farewell(self, greeting, accusative):
         print(greeting, accusative)


def AddGlobalAttribute(classname, methodname):
    print("Adding " + classname + "." + methodname + "()")
    def genericFunction(*args):
        return globals()[classname]().__getattribute__(methodname)(*args)
    globals()[methodname] = genericFunction


# set up the global namespace
x = 0   # X and Y are here to add them implicitly to globals, so
y = 0   # globals does not change as we iterate over it.


toAdd = []


def isCallableMethod(classname, methodname):
    someclass = globals()[classname]()
    something = someclass.__getattribute__(methodname)
    return callable(something)


for x in globals():
    print("Looking at", x)
    if isinstance(globals()[x], (types.ClassType, type)):
        print("Found Class:", x)
        for y in dir(globals()[x]):
            if y.find("__") == -1: # hack to ignore default methods
                if isCallableMethod(x,y):
                    if y not in globals(): # don't override existing global names
                        toAdd.append((x,y))
    # Returns:
    # ('Looking at', 'A')
    # ('Found Class:', 'A')
    # ('Looking at', 'toAdd')
    # ('Looking at', '__builtins__')
    # ('Looking at', 'AddGlobalAttribute')
    # ('Looking at', 'register')
    # ('Looking at', '__package__')
    # ('Looking at', 'salutation')
    # ('Looking at', 'farewell')
    # ('Looking at', 'types')
    # ('Looking at', 'x')
    # ('Looking at', 'y')
    # ('Looking at', '__name__')
    # ('Looking at', 'isCallableMethod')
    # ('Looking at', '__doc__')
    # ('Looking at', 'codecs')



for x in toAdd:
    AddGlobalAttribute(*x)


if __name__ == "__main__":
    salutation("world")
    farewell("goodbye", "world")


# Returns:
# hello world
# goodbye world

This works by iterating over the all the objects in the global namespace. If the item is a class, it iterates over the class attributes. If the attribute is callable it adds it to the global namespace as a function.

It ignore all attributes which contain "__".

I wouldn't use this in production code, but it should get you started.

JayRizzo
  • 3,234
  • 3
  • 33
  • 49
grieve
  • 13,220
  • 10
  • 49
  • 61
  • 2
    I prefer Håvard S's answer to mine, as it appears much cleaner, but this directly answers the question as asked. – grieve May 05 '10 at 20:38
  • This is a lot closer to what I eventually went with. It's a little messy, but works with globals() correctly within the same module. – Matt Joiner May 06 '10 at 02:14
  • 1
    Seems to me like this answer isn't quite what was asked for which was "When calling a function that does not exist in a module's statically defined attributes" because it's doing it's work unconditionally and adding every possible class method. That could be fixed by using to a module wrapper that only does the `AddGlobalAttribute()` when there's a module level `AttributeError` -- kind of the reverse of @Håvard S's logic. If I have a chance I'll test this out and add my own hybrid answer even though the OP has accepted this answer already. – martineau Sep 13 '10 at 18:12
  • Update to my previous comment. I now understand that it's very hard (impssoble?) to intercept `NameError` exceptions for the global (module) namespace -- which explains why this answer adds callables for every possiblity it finds to the current global namespace to cover every possible case ahead of time. – martineau Sep 14 '10 at 01:51
5

Here's my own humble contribution -- a slight embellishment of @Håvard S's highly rated answer, but a bit more explicit (so it might be acceptable to @S.Lott, even though probably not good enough for the OP):

import sys

class A(object):
    def salutation(self, accusative):
        print "hello", accusative

class Wrapper(object):
    def __init__(self, wrapped):
        self.wrapped = wrapped

    def __getattr__(self, name):
        try:
            return getattr(self.wrapped, name)
        except AttributeError:
            return getattr(A(), name)

_globals = sys.modules[__name__] = Wrapper(sys.modules[__name__])

if __name__ == "__main__":
    _globals.salutation("world")
martineau
  • 119,623
  • 25
  • 170
  • 301
-3

Create your module file that has your classes. Import the module. Run getattr on the module you just imported. You can do a dynamic import using __import__ and pull the module from sys.modules.

Here's your module some_module.py:

class Foo(object):
    pass

class Bar(object):
    pass

And in another module:

import some_module

Foo = getattr(some_module, 'Foo')

Doing this dynamically:

import sys

__import__('some_module')
mod = sys.modules['some_module']
Foo = getattr(mod, 'Foo')
drr
  • 143
  • 4