44

Say, I have the following class in Python

class Foo(object):
    a = None
    b = None
    c = None
    def __init__(self, a = None, b = None, c = None):
        self.a = a
        self.b = b
        self.c = c

Is there any way to simplify this process? Whenever I add a new member to class Foo, I'm forced to modify the constructor.

1

4 Answers 4

33

Please note that

class Foo(object):
    a = None

sets a key-value pair in Foo's dict:

Foo.__dict__['a']=None

while

def __init__(self, a = None, b = None, c = None):
    self.a = a

sets a key-value pair in the Foo instance object's dict:

foo=Foo()
foo.__dict__['a']=a

So setting the class members at the top of your definition is not directly related to the setting of the instance attributes in the lower half of your definition (inside the __init__.

Also, it is good to be aware that __init__ is Python's initializer. __new__ is the class constructor.


If you are looking for a way to automatically add some instance attributes based on __init__'s arguments, you could use this:

import inspect
import functools

def autoargs(*include,**kwargs):   
    def _autoargs(func):
        attrs,varargs,varkw,defaults=inspect.getargspec(func)
        def sieve(attr):
            if kwargs and attr in kwargs['exclude']: return False
            if not include or attr in include: return True
            else: return False            
        @functools.wraps(func)
        def wrapper(self,*args,**kwargs):
            # handle default values
            for attr,val in zip(reversed(attrs),reversed(defaults)):
                if sieve(attr): setattr(self, attr, val)
            # handle positional arguments
            positional_attrs=attrs[1:]            
            for attr,val in zip(positional_attrs,args):
                if sieve(attr): setattr(self, attr, val)
            # handle varargs
            if varargs:
                remaining_args=args[len(positional_attrs):]
                if sieve(varargs): setattr(self, varargs, remaining_args)                
            # handle varkw
            if kwargs:
                for attr,val in kwargs.iteritems():
                    if sieve(attr): setattr(self,attr,val)            
            return func(self,*args,**kwargs)
        return wrapper
    return _autoargs

So when you say

class Foo(object):
    @autoargs()
    def __init__(self,x,path,debug=False,*args,**kw):
        pass
foo=Foo('bar','/tmp',True, 100, 101,verbose=True)

you automatically get these instance attributes:

print(foo.x)
# bar
print(foo.path)
# /tmp
print(foo.debug)
# True
print(foo.args)
# (100, 101)
print(foo.verbose)
# True

PS. Although I wrote this (for fun), I don't recommend using autoargs for serious work. Being explicit is simple, clear and infallible. I can't say the same for autoargs.

PPS. Is it just me, or are a lot of buttons broken on Stackoverflow? The editor window has lost all its icons... :( Clearing the browser cache fixed the problem.

Sign up to request clarification or add additional context in comments.

2 Comments

I forgot to add - thanks for clearing up my python terminologies.
Shouldn't this be in the standard library? I don't see the point of setting on every init...
23

Python 3.7 provides dataclasses which are helpful in situations like this:

from dataclasses import dataclass


@dataclass
class Foo:
    a: str = None
    b: str = None
    c: str = None

This saves you from having to write out the __init__ method when you just want to store a few attributes.

Gives you a good __repr__ method:

>>> a = Foo()
>>> a
Foo(a=None, b=None, c=None)

If you need to do calculations on a param, you can implement __post_init__.

See also namedtuple:

from collections import namedtuple

Foo = namedtuple('Foo', ['a', 'b', 'c'])

All fields are required with namedtuple though.

>>> a = Foo(1, 2, 3)
>>> a
Foo(a=1, b=2, c=3)

1 Comment

This really should be the new accepted answer. All the other suggestions are ugly and unnecessary in the modern era.
14

There are elegant ways to do this.

Is there any way to simplify this process? Whenever I add a new member to class Foo, I'm forced to modify the constructor.

There is also a crude way. It will work, but is NOT recommended. See and decide.

>>> class Foo(object):
    def __init__(self, **attrs):
        self.__dict__.update(**attrs)
    def __getattr__(self, attr):
        return self.__dict__.get(attr, None)


>>> f = Foo(a = 1, b = 2, c = 3)
>>> f.a, f.b
(1, 2)
>>> f = Foo(bar = 'baz')
>>> f.bar
'baz'
>>> f.a
>>> 

The keyword argument constructor lets you get away without explicitly defining any arguments. Warning: this goes against the "explicit is better than implicit" principle.

You need to override __getattr__ ONLY if you want to return a default value for an attribute that is not present instead of getting an AttributeError.

1 Comment

perfect! the most elegant way to do this.
-1

http://code.activestate.com/recipes/286185-automatically-initializing-instance-variables-from/

This recipe and its comments provide some methods.

Python: Automatically initialize instance variables?

This is a previous question.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.