tags:

views:

55

answers:

3

Hi, I want to instrument a class via a decorator, to add some instance variables that are specified by the author of the class.

I started with the following code, but this just adds class variables and I want instance variables (those that are normally 'declared' in __ init __)

What is a pythonic way to do this, while allowing the class author control over what they put in __ init __?

def add_list_attributes(klass):
    for attribute in klass.list_attributes:
        setattr(klass, attribute, [])

    return klass

@add_list_attributes
class Person(object):

    list_attributes = [
        'phone_numbers'
    ]

    def __init__(self):
        pass

p1 = Person()
p1.phone_numbers.append('01234')

print p1.phone_numbers
+2  A: 

You would have to wrap __init__() in a separate function that would call the original method, then add its own attributes to the first argument.

Ignacio Vazquez-Abrams
+2  A: 

You can do this by wrapping the __init__ method to do your bidding, and then call the original __init__:

def add_list_attributes(klass):
    old_init = klass.__init__
    def new_init(self, *args, **kwargs):
        for attribute in klass.list_attributes:
            setattr(self, attribute, [])
        old_init(self, *args, **kwargs)
    klass.__init__ = new_init
    return klass

@add_list_attributes
class Person(object):

    list_attributes = [
        'phone_numbers'
    ]

    def __init__(self):
        pass

p1 = Person()
p1.phone_numbers.append('01234')
p2 = Person()
p2.phone_numbers.append('56789')

print p1.phone_numbers
print p2.phone_numbers
Mike Boers
You might use the `functools.wraps` decorator to carry over the name and docstring of the original `__init__`.
Philipp
+2  A: 

You can create a custom __new__ method:

def add_list_attributes(klass):
    def new(cls, *args, **kwargs):
        result = super(cls, cls).__new__(cls)
        for attribute in klass.list_attributes:
            setattr(result, attribute, [])
        return result
    klass.__new__ = staticmethod(new)
    return klass

@add_list_attributes
class Person(object):
    list_attributes = [
        'phone_numbers'
    ]
    def __init__(self):
        pass

p1 = Person()
p2 = Person()
p1.phone_numbers.append('01234')

print p1.phone_numbers, p2.phone_numbers
Philipp
`super` is the way to go here; +1
Mike Boers
But I'm not sure whether this works if the superclass `__init__` function takes more than one argument.
Philipp
@Philipp: then why not pass on the args/kwargs to the `__new__` call?: `super(cls, cls).__new__(cls, *args, **kwargs)`
Mike Boers