Pergunta

Suppose I have a python class

class Foo(object):
    def method1(self, x):
        print("method1 called with %s" %(x,))

    def method2(self, x):
        print("method2 called with %s" %(x,))

and a decorator

def myDecorator(func):
    newFunc = someModificationOf(func)
    return newFunc

I would like to be able to subclass Foo and somehow in the subclass definition indicate that I want myDecorator to be applied to some subset of the methods inherited from Foo. For example, my subclass could like like

class Baz(Foo):
    methodsToDecorate = ['method2']

Is this possible?

What I tried:

I thought I could do this with a metaclass:

class Baz(Foo):
    __metaclass__ = Meta
    methodsToDecorate = ['method2']

class Meta(type):
    def __new__(cls, name, bases, d):
        for m in d['methodsToDecorate']:
            d[m] = myDecorator(d[m])
        return type(name, bases, d)

However, it doesn't work because 'method2' is not in the dictionary passed into the metaclass's __new__ method, so we get a KeyError. I like the idea of using a metaclass because it allows the decorator to manipulate functions, rather than methods, which seems like a good simplification.

Foi útil?

Solução

I'm assuming what you really want to do is not decorate the base class's copies of those methods, but decorate new overriding copies of those methods in the subclass.

If so, that's easy. The methods don't exist in the dictionary passed to the metaclass because they're not defined in the class definition, they're inherited from the bases. Which the metaclass also has access to. So:

def __new__(cls, name, bases, d):
    def find_method(m):
        for base in bases:
            try:
                return getattr(base, m)
            except AttributeError:
                pass
        raise AttributeError("No bases have method '{}'".format(m))
    for m in d['methodsToDecorate']:
        d[m] = myDecorator(find_method(m))
    return type(name, bases, d)

If you really do want to reach into the base class and replace its methods, you can of course do that using the same trick. Just return the base along with the found method, so you can setattr it.


You asked for overriding a method in the base class. But what if the method actually comes from a more distant ancestor? Then this will still work… but it may not work the way you wanted. If you have multiple bases that share a common ancestor, calling getattr on each direct base will search that common ancestor once per base, but calling getattr on the new class will only search it once at the end.

As user2357112 points out in a comment, if what you want to override is "whatever would have been called if I didn't override anything", you can do that by constructing the new class first, then modifying it after the fact, by calling getattr on the result class itself. In 2.x, this does mean you have to deal with unbound methods instead of functions (in 3.x, that's not an issue, because unbound methods just are functions), but the benefits probably outweigh the cost.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top