42

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.

Jeeyoung Kim
  • 5,827
  • 8
  • 44
  • 54
  • 2
    I also found that subclassing namedtuple - http://docs.python.org/dev/library/collections.html#collections.namedtuple isn't a bad idea. – Jeeyoung Kim Nov 06 '10 at 21:59

4 Answers4

34

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.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
20

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)
dstandish
  • 2,328
  • 18
  • 34
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.

Community
  • 1
  • 1
Manoj Govindan
  • 72,339
  • 21
  • 134
  • 141
-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.

Community
  • 1
  • 1
li.davidm
  • 11,736
  • 4
  • 29
  • 31