1

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

3 Answers 3

2

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
Sign up to request clarification or add additional context in comments.

4 Comments

super is the way to go here; +1
But I'm not sure whether this works if the superclass __init__ function takes more than one argument.
then why not pass on the args/kwargs to the __new__ call?: super(cls, cls).__new__(cls, *args, **kwargs)
where can i find definition for setattr and staticmethod
2

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.

Comments

2

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

1 Comment

You might use the functools.wraps decorator to carry over the name and docstring of the original __init__.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.