views:

5550

answers:

4

In Python 2.5, is there a way to create a decorator that decorates a class? Specifically, I want to use a decorator to add a member to a class and change the constructor to take a value for that member.

Looking for something like the following (which has a syntax error on 'class Foo:':

def getId(self): return self.__id

class addID(original_class):
    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        original_class.__init__(self, *args, **kws)

@addID
class Foo:
    def __init__(self, value1):
        self.value1 = value1

if __name__ == '__main__':
    foo1 = Foo(5,1)
    print foo1.value1, foo1.getId()
    foo2 = Foo(15,2)
    print foo2.value1, foo2.getId()

Thanks, Rob

Edit: Rereading this question, I guess what I'm really after is a way to do something like a C# interface in Python. I need to switch my paradigm I suppose.

A: 

Is there a reason you're not using inheritance?

Horn
+8  A: 

That's not a good practice and there is no mechanism to do that because of that. The right way to accomplish what you want is inheritance.

Take a look into the class documentation.

A little example:

class Employee(object):

    def __init__(self, age, sex, siblings=0):
        self.age = age
        self.sex = sex    
        self.siblings = siblings

    def born_on(self):    
        today = datetime.date.today()

        return today - datetime.timedelta(days=self.age*365)


class Boss(Employee):    
    def __init__(self, age, sex, siblings=0, bonus=0):
        self.bonus = bonus
        Employee.__init__(self, age, sex, siblings)

This way Boss has everything Employee has, with also his own __init__ method and own members.

mpeterson
I guess what I wanted to was have Boss agnostic of the class that it contains. That is, there may be dozens of different classes that I want to apply Boss features to. Am I left with having these dozen classes inherit from Boss?
Robert Gowland
@Robert Gowland: That's why Python has multiple inheritance. Yes, you should inherit various aspects from various parent classes.
S.Lott
@S.Lott: In general multiple inheritance is a bad idea, even too much levels of inheritance is bad too. I shall recommend you to stay away from multiple inheritance.
mpeterson
mpeterson: Is multiple inheritance in python worse than this approach? What's wrong with python's multiple inheritance?
Arafangion
+12  A: 

I would second the notion that you may wish to consider a subclass instead of the approach you've outlined. However, not knowing your specific scenario, YMMV :-)

What you're thinking of is a metaclass. The __new__ function in a metaclass is passed the full proposed definition of the class, which it can then rewrite before the class is created. You can, at that time, sub out the constructor for a new one.

Example:

def substitute_init(self, id, *args, **kwargs):
    pass

class FooMeta(type):

    def __new__(cls, name, bases, attrs):
        attrs['__init__'] = substitute_init
        return super(FooMeta, cls).__new__(cls, name, bases, attrs)

class Foo(object):

    __metaclass__ = FooMeta

    def __init__(self, value1):
        pass

Replacing the constructor is perhaps a bit dramatic, but the language does provide support for this kind of deep introspection and dynamic modification.

Jarret Hardie
Thank-you, that's what I'm looking for. A class that can modify any number of other classes such that they all have a particular member.My reasons for not having the classes inherit from a common ID class is that I want to have non-ID versions of the classes as well as ID versions.
Robert Gowland
+14  A: 

Apart from the question whether class decorators are the right solution to your problem:

in Python 2.6 and higher, there are class decorators with the @-syntax, so you can write:

@addID
class Foo:
    pass

in older versions, you can do it another way:

class Foo:
    pass

Foo = addID(Foo)

Note however that this works the same as for function decorators, and that the decorator should return the new (or modified original) class, which is not what you're doing in the example. The addID decorator would look like this:

def addID(original_class):
    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        original_class.__init__(self, *args, **kws)

    original_class.__init__ = __init__
    return original_class

You could then use the appropriate syntax for your python version as described above.

But I agree with others that inheritance is better suited if you want to override __init__.

Steven
+1 for noting that Python 2.6+ has class decorators.
Jarret Hardie
... even though the question specifically mentions Python 2.5 :-)
Jarret Hardie
Your example causes a RuntimeError: maximum recursion depth exceeded, because when the class is instantiated, original_class.__init__ is the function that is called, therefore having __init__ call itself. I'll post a working example in the next comment.
Gerald Senarclens de Grancy
Sorry for the linesep mess, but code samples aren't strictly great in comments...:def addID(original_class): original_class.__orig__init__ = original_class.__init__ def __init__(self, *args, **kws): print "decorator" self.id = 9 original_class.__orig__init__(self, *args, **kws) original_class.__init__ = __init__ return original_class@addIDclass Foo: def __init__(self): print "Foo"a = Foo()print a.id
Gerald Senarclens de Grancy