views:

762

answers:

5

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

def __getattr__(mod, name):
    return getattr(A(), name)

if __name__ == "__main__":
    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

Evidently something is not right about my assumed implementation.

+8  A: 

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
nice and dirty :D
Matt Joiner
That may work but it's probably not a solution to the real problem of the author.
DasIch
"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
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
Unfortunately this doesn't work for the current module, or likely for stuff accessed after an `import *`.
Matt Joiner
+4  A: 

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
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
@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
+5  A: 

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.

Matt Anderson
A: 

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
You're answering a different question here.
Marius Gedminas
+1  A: 

This is hackish, but...

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))


for x in toAdd:
    AddGlobalAttribute(*x)


if __name__ == "__main__":
    salutation("world")
    farewell("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.

grieve
I prefer Håvard S's answer to mine, as it appears much cleaner, but this directly answers the question as asked.
grieve
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
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
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