172

I see patterns like

def __init__(self, x, y, z):
    ...
    self.x = x
    self.y = y
    self.z = z
    ...

quite frequently, often with a lot more parameters. Is there a good way to avoid this type of tedious repetitiveness? Should the class inherit from namedtuple instead?

9
  • 31
    Not all receptiveness is bad. Keep in mind that Python's class model doesn't include explicit definition of instance attributes, so these assignments are the self-documenting equivalents.
    – chepner
    Commented Feb 4, 2016 at 1:33
  • 4
    @chepner: Well, doesn't require explicit definition. You can use __slots__ for the purpose though; it's mildly unpythonic (more verbose to get memory savings), but I like it largely to avoid the risk of auto-vivifying a whole new attribute if I typo the name. Commented Feb 4, 2016 at 3:30
  • 2
    Any good editor will have templates. You type ini <shortcut> x, y, z): <shortcut> and you are done.
    – Gere
    Commented Feb 4, 2016 at 5:52
  • 3
    Namedtuples are awesome, if you want an immutable value object. If you want a regular, mutable class, you can't use them. Commented Feb 4, 2016 at 15:16
  • 4
    "Don't" is a good option, any option available will kill the method signature (and thus potentially the whole interface). Besides, if your classes have a unbearable amount of fields to initialize, you might want to consider splitting them.
    – Kroltan
    Commented Feb 5, 2016 at 0:13

10 Answers 10

108

Disclaimer: It seems that several people are concerned about presenting this solution, so I will provide a very clear disclaimer. You should not use this solution. I only provide it as information, so you know that the language is capable of this. The rest of the answer is just showing language capabilities, not endorsing using them in this way.


There isn't really anything wrong with explicitly copying parameters into attributes. If you have too many parameters in the ctor, it is sometimes considered a code smell and maybe you should group these params into a fewer objects. Other times, it is necessary and there is nothing wrong with it. Anyway, doing it explicitly is the way to go.

However, since you are asking HOW it can be done (and not whether it should be done), then one solution is this:

class A:
    def __init__(self, **kwargs):
        for key in kwargs:
          setattr(self, key, kwargs[key])

a = A(l=1, d=2)
a.l # will return 1
a.d # will return 2
12
  • 16
    good answer +1 ... although self.__dict__.update(kwargs) might be marginally more pythonic Commented Feb 4, 2016 at 1:30
  • 46
    The problem with this approach is that there is no record of what arguments A.__init__ actually expects, and no error checking for mistyped argument names.
    – MWB
    Commented Feb 4, 2016 at 3:09
  • 7
    @JoranBeasley Updating the instance dictionary blindly with kwargs leaves you open to the equivalent of an SQL injection attack. If your object has a method named my_method and you pass an argument named my_method to the constructor, then update() the dictionary, you just overwrote the method.
    – Pedro
    Commented Feb 4, 2016 at 4:47
  • 3
    As others said, the suggestion is really poor programming style. It hides crucial information. You can show it, but you should explicitely discourage the OP from using it.
    – Gere
    Commented Feb 4, 2016 at 5:51
  • 3
    @Pedro Is there any semantic difference between gruzczy's and JoranBeasley's syntax?
    – gerrit
    Commented Feb 4, 2016 at 8:26
87

Edit: If you have python 3.7+ just use dataclasses

A decorator solution that keeps the signature:

import decorator
import inspect
import sys


@decorator.decorator
def simple_init(func, self, *args, **kws):
    """
    @simple_init
    def __init__(self,a,b,...,z)
        dosomething()

    behaves like

    def __init__(self,a,b,...,z)
        self.a = a
        self.b = b
        ...
        self.z = z
        dosomething()
    """

    #init_argumentnames_without_self = ['a','b',...,'z']
    if sys.version_info.major == 2:
        init_argumentnames_without_self = inspect.getargspec(func).args[1:]
    else:
        init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]

    positional_values = args
    keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)
    attribute_values = positional_values + keyword_values_in_correct_order

    for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):
        setattr(self,attribute_name,attribute_value)

    # call the original __init__
    func(self, *args, **kws)


class Test():
    @simple_init
    def __init__(self,a,b,c,d=4):
        print(self.a,self.b,self.c,self.d)

#prints 1 3 2 4
t = Test(1,c=2,b=3)
#keeps signature
#prints ['self', 'a', 'b', 'c', 'd']
if sys.version_info.major == 2:
    print(inspect.getargspec(Test.__init__).args)
else:
    print(inspect.signature(Test.__init__))
7
  • 2
    nice answer, but won't work with python2.7: no signature
    – MWB
    Commented Feb 4, 2016 at 13:56
  • 3
    @alexis the "decorator.decorator" decorator automatically wraps the function
    – Siphor
    Commented Feb 4, 2016 at 16:38
  • 4
    I'm quite torn about whether to love this or hate it. I do appreciate preserving the signature. Commented Feb 4, 2016 at 21:20
  • 14
    "... Explicit is better than implicit. Simple is better than complex. ..." -Zen of Python
    – user521945
    Commented Feb 9, 2016 at 23:35
  • 13
    -1 Quite frankly this is horrible. I have no idea what this code is doing at a glance, and it's literally ten times the amount of code. Being clever feels cool and all, but this is a misuse of your obvious cleverness.
    – Ian Newson
    Commented Feb 10, 2016 at 21:21
29

As others have mentioned, the repetition isn't bad, but in some cases a namedtuple can be a great fit for this type of issue. This avoids using locals() or kwargs, which are usually a bad idea.

from collections import namedtuple
# declare a new object type with three properties; x y z
# the first arg of namedtuple is a typename
# the second arg is comma-separated or space-separated property names
XYZ = namedtuple("XYZ", "x, y, z")

# create an object of type XYZ. properties are in order
abc = XYZ("one", "two", 3)
print abc.x
print abc.y
print abc.z

I've found limited use for it, but you can inherit a namedtuple as with any other object (example continued):

class MySuperXYZ(XYZ):
    """ I add a helper function which returns the original properties """
    def properties(self):
        return self.x, self.y, self.z

abc2 = MySuperXYZ(4, "five", "six")
print abc2.x
print abc2.y
print abc2.z
print abc2.properties()
6
  • 5
    These are tuples, so your properties method can be written as just return tuple(self), which is more maintainable if in future more fields are added to the namedtuple definition.
    – PaulMcG
    Commented Feb 4, 2016 at 3:52
  • 1
    Also, your namedtuple declaration string does not require commas between the fieldnames, XYZ = namedtuple("XYZ", "x y z") works just as well.
    – PaulMcG
    Commented Feb 4, 2016 at 3:55
  • Thanks @PaulMcGuire. I was trying to think of a really simple add-on to show the inheritance and kind of spaced on that. You're 100% right and it's a great shorthand with other inherited objects, too! I do mention the field names can be comma or space separated -- I prefer CSV from habit Commented Feb 4, 2016 at 3:56
  • 1
    I often use namedtuples for this exact purpose, especially in mathematical code where a function might be highly parametrised and have a bunch of coefficients that only make sense together.
    – detly
    Commented Feb 4, 2016 at 5:03
  • The problem with namedtuple is that they are read-only. You can not do abc.x += 1 or anything like that. Commented Feb 4, 2016 at 11:26
29

Explicit is better than implicit ... so sure, you could make it more concise:

def __init__(self,a,b,c):
    for k,v in locals().items():
        if k != "self":
             setattr(self,k,v)

The better question is: should you?

... that said if you want a named tuple I would recommend using a namedtuple (remember tuples have certain conditions attached to them) ... perhaps you want an OrderedDict or even just a dict ...

4
  • Then the object will need cyclic garbage collection since it has itself as an attribute Commented Feb 4, 2016 at 1:30
  • 3
    @bernie (or is it bemie?), sometimes ke r ning is hard
    – cat
    Commented Feb 4, 2016 at 2:25
  • 4
    For slightly more efficient tests, if k != "self": could be changed to if v is not self:, cheap identity test, rather than string comparison. I suppose technically __init__ could be called a second time after construction and passed self as a subsequent argument, but I really don't want to think what sort of monster would do that. :-) Commented Feb 4, 2016 at 3:32
  • That could be made into a function which takes the return value of locals: set_fields_from_locals(locals()). Then it's no longer than the more magical decorator based solutions.
    – Lii
    Commented Feb 17, 2016 at 15:45
21

To expand on gruszczys answer, I have used a pattern like:

class X:
    x = None
    y = None
    z = None
    def __init__(self, **kwargs):
        for (k, v) in kwargs.items():
            if hasattr(self, k):
                setattr(self, k, v)
            else:
                raise TypeError('Unknown keyword argument: {:s}'.format(k))

I like this method because it:

  • avoids repetition
  • is resistant against typos when constructing an object
  • works well with subclassing (can just super().__init(...))
  • allows for documentation of the attributes on a class-level (where they belong) rather than in X.__init__

Prior to Python 3.6, this gives no control over the order in which the attributes are set, which could be a problem if some attributes are properties with setters that access other attributes.

It could probably be improved upon a bit, but I'm the only user of my own code so I am not worried about any form of input sanitation. Perhaps an AttributeError would be more appropriate.

0
10

You could also do:

locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
    setattr(self, arg, locs[arg])

Of course, you would have to import the inspect module.

8

This is a solution without any additional imports.

Helper function

A small helper function makes it more convenient and re-usable:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    self = local_name_space.pop('self')
    for name, value in local_name_space.items():
        setattr(self, name, value)

Application

You need to call it with locals():

class A:
    def __init__(self, x, y, z):
        auto_init(locals())

Test

a = A(1, 2, 3)
print(a.__dict__)

Output:

{'y': 2, 'z': 3, 'x': 1}

Without changing locals()

If you don't like to change locals() use this version:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    for name, value in local_name_space.items():
        if name != 'self': 
            setattr(local_name_space['self'], name, value)
5
  • docs.python.org/2/library/functions.html#locals locals() should not be modified (it may affect the interpreter, in your case, removing self from the calling function's scope)
    – MWB
    Commented Dec 11, 2016 at 1:27
  • @MaxB From the docs you cite: ... changes may not affect the values of local and free variables used by the interpreter. self is still available in __init__ . Commented Dec 11, 2016 at 2:28
  • Right, the reader expects it to affect the local variables, but it may or may not, depending on a number of circumstances. The point is that it's UB.
    – MWB
    Commented Dec 11, 2016 at 2:30
  • Quote: "The contents of this dictionary should not be modified"
    – MWB
    Commented Dec 11, 2016 at 2:32
  • @MaxB I added a version that does not change locals(). Commented Dec 11, 2016 at 2:57
8

Python 3.7 onwards

In Python 3.7, you may (ab)use the dataclass decorator, available from the dataclasses module. From the documentation:

This module provides a decorator and functions for automatically adding generated special methods such as __init__() and __repr__() to user-defined classes. It was originally described in PEP 557.

The member variables to use in these generated methods are defined using PEP 526 type annotations. For example this code:

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

Will add, among other things, a __init__() that looks like:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
      self.name = name
      self.unit_price = unit_price
      self.quantity_on_hand = quantity_on_hand

Note that this method is automatically added to the class: it is not directly specified in the InventoryItem definition shown above.

If your class is large and complex, it may be inappropriate to use a dataclass. I'm writing this on the day of release of Python 3.7.0, so usage patterns are not yet well established.

1
  • Since 3.6 and below are now end-of-life, this should be the preferred answer. Commented Sep 22, 2022 at 17:08
7

An interesting library that handles this (and avoids a lot of other boilerplate) is attrs. Your example, for instance, could be reduced to this (assume the class is called MyClass):

import attr

@attr.s
class MyClass:
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()

You don't even need an __init__ method anymore, unless it does other stuff as well. Here's a nice introduction by Glyph Lefkowitz.

2
5

My 0.02$. It is very close to Joran Beasley answer, but more elegant:

def __init__(self, a, b, c, d, e, f):
    vars(self).update((k, v) for k, v in locals().items() if v is not self)

Additionally, Mike Müller's answer (the best one to my taste) can be reduced with this technique:

def auto_init(ns):
    self = ns.pop('self')
    vars(self).update(ns)

And the just call auto_init(locals()) from your __init__

1

Not the answer you're looking for? Browse other questions tagged or ask your own question.