171

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?

bobcat
  • 9,713
  • 6
  • 34
  • 72
  • 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 Feb 04 '16 at 01:33
  • 4
    @chepner: Well, doesn't _require_ explicit definition. You can use [`__slots__` for the purpose though](https://docs.python.org/3/reference/datamodel.html#slots); 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. – ShadowRanger Feb 04 '16 at 03:30
  • 2
    Any good editor will have templates. You type `ini x, y, z): ` and you are done. – Gerenuk Feb 04 '16 at 05:52
  • 3
    Namedtuples are awesome, _if_ you want an immutable value object. If you want a regular, mutable class, you can't use them. – RemcoGerlich Feb 04 '16 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 Feb 05 '16 at 00:13
  • @RemcoGerlich a class derived from `namedtuple` can have mutable attributes (in addition to the immutable inherited ones) – bobcat Feb 05 '16 at 01:13
  • Remember that this pattern also allows you to set defaults for x/y/z. Alternatives don't. And besides, it's only tedious if it's a substantial part of the work. I've been in this industry for 20 years, and I've never yet seen a constructor where copying parameters was a significant portion of the whole file! – Graham Feb 05 '16 at 12:20
  • @Kroltan I would like to see your comment as an answer. – Jack Stout Feb 09 '16 at 23:38
  • 1
    @JackStout It wouldn't be a proper answer. He is asking for a *how to do it*, not *should I do it*. – Kroltan Feb 10 '16 at 00:01
  • To the OP: it might help if you explained why you want to avoid this? – Ian Newson Feb 10 '16 at 21:22
  • Rather than this being a common pattern, init methods often only **start** with this pattern. As you keep working with it, you will almost certainly need to do some additional logic with at least some args, and therefore with an auto-setting approach you will need to now do processing of self. properties you've just set, so that overall init code looks more complicated than the alternative. – Rainy Feb 13 '16 at 23:48

11 Answers11

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
Rakete1111
  • 42,521
  • 11
  • 108
  • 141
gruszczy
  • 37,239
  • 27
  • 119
  • 167
  • 16
    good answer +1 ... although `self.__dict__.update(kwargs)` might be marginally more pythonic – Joran Beasley Feb 04 '16 at 01:30
  • 45
    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. – bobcat Feb 04 '16 at 03: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 Feb 04 '16 at 04: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. – Gerenuk Feb 04 '16 at 05:51
  • I believe the first two paragraph discourage from using the method. The user asked for how something is done and I think he/she received enough feedback that he/she shouldn't do that. It should still be useful for him/her to learn about this approach, even if he/she won't use it. – gruszczy Feb 04 '16 at 06:01
  • I also bolded the sentence that clearly states that the author of the question should do this explicitly. – gruszczy Feb 04 '16 at 06:16
  • You are right, sorry. I had the suspicion that the user might not realize how bad it would be. – Gerenuk Feb 04 '16 at 06:43
  • 3
    @Pedro Is there any semantic difference between gruzczy's and JoranBeasley's syntax? – gerrit Feb 04 '16 at 08:26
  • @gerrit The only thing I can think of (I'm typing on my phone so I can't test) is that `setattr()` might trigger setter logic if you have a `@property.setter` in your object, whereas the dictionary update won't. Other than that, they are equivalent. – Pedro Feb 04 '16 at 12:10
  • 1
    holy crap ... I should have put more effort into my answer ... no idea it would get 42 upvotes ... – Joran Beasley Feb 04 '16 at 16:23
  • What's wrong with it is that it is extra lines of code without improving readability. If that's pythonic then okay, but it doesn't mean there's nothing wrong with it. – djechlin Feb 05 '16 at 00:41
  • I added one more disclaimer that this is only to show capabilities of the language and it shouldn't be used in real projects – gruszczy Feb 05 '16 at 03:24
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__))
Siphor
  • 2,484
  • 2
  • 11
  • 10
  • 2
    nice answer, but won't work with python2.7: no `signature` – bobcat Feb 04 '16 at 13:56
  • Shouldn't `simple_init`, as a decorator, **return** the decorated function instead of calling it? – alexis Feb 04 '16 at 16:00
  • 3
    @alexis the "decorator.decorator" decorator automatically wraps the function – Siphor Feb 04 '16 at 16:38
  • 4
    I'm quite torn about whether to love this or hate it. I do appreciate preserving the signature. – Kyle Strand Feb 04 '16 at 21:20
  • 14
    "... Explicit is better than implicit. Simple is better than complex. ..." -Zen of Python – Jack Stout Feb 09 '16 at 23:35
  • 10
    -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 Feb 10 '16 at 21:21
30

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 ...

Ɖiamond ǤeezeƦ
  • 3,094
  • 3
  • 27
  • 40
Joran Beasley
  • 93,863
  • 11
  • 131
  • 160
  • Then the object will need cyclic garbage collection since it has itself as an attribute – John La Rooy Feb 04 '16 at 01:30
  • 3
    @bernie (or is it bemie?), sometimes ke r ning is hard – cat Feb 04 '16 at 02: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. :-) – ShadowRanger Feb 04 '16 at 03: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 Feb 17 '16 at 15:45
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()
  • 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 Feb 04 '16 at 03: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 Feb 04 '16 at 03: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 – A Small Shell Script Feb 04 '16 at 03:56
  • 1
    I often use `namedtuple`s 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 Feb 04 '16 at 05:03
  • The problem with `namedtuple` is that they are read-only. You can not do `abc.x += 1` or anything like that. – hamstergene Feb 04 '16 at 11:26
  • @hamstergene: it's solely a good thing. ;) The only downside is that you have to write your own `update` methods (that return a fresh copy with a particular field updated) – mike3996 Feb 04 '16 at 12:31
20

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.

gerrit
  • 17,590
  • 12
  • 72
  • 135
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.

zondo
  • 18,070
  • 7
  • 35
  • 73
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)
Mike Müller
  • 71,943
  • 15
  • 139
  • 140
  • https://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) – bobcat Dec 11 '16 at 01: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__` . – Mike Müller Dec 11 '16 at 02: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. – bobcat Dec 11 '16 at 02:30
  • Quote: "The contents of this dictionary should not be modified" – bobcat Dec 11 '16 at 02:32
  • @MaxB I added a version that does not change locals(). – Mike Müller Dec 11 '16 at 02:57
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.

RafG
  • 960
  • 9
  • 15
  • To what degree is the functionaly of `attr` made redundant by [`dataclasses`](https://docs.python.org/3.7/library/dataclasses.html#module-dataclasses)? – gerrit Jun 27 '18 at 14:04
  • 1
    @gerrit This is discussed in the [documentation of the attrs package](https://www.attrs.org/en/stable/why.html#data-classes). Tbh, the differences don't seem that large anymore. – Ivo Merchiers Mar 28 '19 at 14:15
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__

bgusach
  • 13,019
  • 10
  • 44
  • 61
  • 1
    https://docs.python.org/2/library/functions.html#locals `locals()` should not be modified (undefined behavior) – bobcat Dec 11 '16 at 01:29
4

It's a natural way to do things in Python. Don't try to invent something more clever, it will lead to overly clever code that no one on your team will understand. If you want to be a team player and then keep writing it this way.

Peter Krumins
  • 885
  • 1
  • 7
  • 22
4

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.

gerrit
  • 17,590
  • 12
  • 72
  • 135