26

I've noticed that a common pattern I use is to assign SomeClass.__init__() arguments to self attributes of the same name. Example:

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

In fact it must be a common task for others as well as PyDev has a shortcut for this - if you place the cursor on the parameter list and click Ctrl+1 you're given the option to Assign parameters to attributes which will create that boilerplate code for you.

Is there a different, short and elegant way to perform this assignment?

Jonathan
  • 84,911
  • 94
  • 244
  • 345
  • 6
    personal opinion: boilerplate code such as this is a common indication of a missing language construct... – Jonathan Dec 30 '11 at 18:48
  • hmmm.... could it be done with a decorator? – Jonathan Dec 30 '11 at 18:51
  • almost any alternative will prevent PyDev's `Ctrl+Space` to auto-complete these attributes... such a shame... – Jonathan Dec 30 '11 at 18:52
  • 1
    Yes you can do it with a decorator, but doing it explicitly will be better for you on the long run. Also it will not mess up PyDevs autocompletion if you do it explicitly – ojii Dec 30 '11 at 19:05
  • 11
    Doing it manually builds character. – derekerdmann Dec 30 '11 at 19:18
  • 4
    http://www.python.org/dev/peps/pep-0020/ (in particular, "explicit is better than implicit", "simple is better than complex", "readability counts"). –  Dec 30 '11 at 19:20
  • 1
    Most of the constructor parameters *my* classes take *shouldn't* be copied into `self` under the same name without transformation. I make stuff private, validate values, normalize them, fill in mutable objects which would be default values if default arguments worked differently, etc. –  Dec 30 '11 at 19:48
  • +1 @derekerdmann Thanks, Dad. – Droogans Dec 30 '11 at 20:00
  • If Eclipse already does this for you, then why try to fix what isn't broken? – Droogans Dec 30 '11 at 20:02
  • 1
    @Droogans - there are those who believe that depending on the IDE instead of being able to code with ease with a text editor is associated with the dark side :) – Jonathan Jan 01 '12 at 17:19
  • I agree with @senderle's answer (especially the bit about needing to refactor if you have many params). However there are rare cases when having many params is the correct approach (consider __init__ of an sklearn estimator). In this case I suggest using an editor with vertical editing capabilities and auto complete. I use Intellij with vim bindings, no problem! – Chris Oct 20 '17 at 15:19

10 Answers10

9

I sympathize with your sense that boilerplate code is a bad thing. But in this case, I'm not sure there even could be a better alternative. Let's consider the possibilities.

If you're talking about just a few variables, then a series of self.x = x lines is easy to read. In fact, I think its explicitness makes that approach preferable from a readability standpoint. And while it might be a slight pain to type, that alone isn't quite enough to justify a new language construct that might obscure what's really going on. Certainly using vars(self).update() shenanigans would be more confusing than it's worth in this case.

On the other hand, if you're passing nine, ten, or more parameters to __init__, you probably need to refactor anyway. So this concern really only applies to cases that involve passing, say, 5-8 parameters. Now I can see how eight lines of self.x = x would be annoying both to type and to read; but I'm not sure that the 5-8 parameter case is common enough or troublesome enough to justify using a different method. So I think that, while the concern you're raising is a good one in principle, in practice, there are other limiting issues that make it irrelevant.

To make this point more concrete, let's consider a function that takes an object, a dict, and a list of names, and assigns values from the dict to names from the list. This ensures that you're still being explicit about which variables are being assigned to self. (I would never suggest a solution to this problem that didn't call for an explicit enumeration of the variables to be assigned; that would be a rare-earth bug magnet):

>>> def assign_attributes(obj, localdict, names):
...     for name in names:
...         setattr(obj, name, localdict[name])
...
>>> class SomeClass():
...     def __init__(self, a, b, c):
...         assign_attributes(self, vars(), ['a', 'b', 'c'])

Now, while not horribly unattractive, this is still harder to figure out than a straightforward series of self.x = x lines. And it's also longer and more trouble to type than one, two, and maybe even three or four lines, depending on circumstances. So you only get certain payoff starting with the five-parameter case. But that's also the exact moment that you begin to approach the limit on human short-term memory capacity (= 7 +/- 2 "chunks"). So in this case, your code is already a bit challenging to read, and this would only make it more challenging.

senderle
  • 125,265
  • 32
  • 201
  • 223
  • Since we're talking about arbitrary numbers of attributes, in python3, using `locals().keys()` is much more efficient than listing then names of the attributes manually. ex: `assignAttributes(self, vars(), locals().keys())` – Skyler Jun 13 '18 at 15:00
  • @Skyler, I think you may have missed my main premise. I specifically think it's a bad idea to name the attributes of a class implicitly. The names of attributes should be specified explicitly. It ensures a minimal standard of self-documentation. Have you ever tried to read code that does something like this? Not fun! – senderle Jun 13 '18 at 15:07
7

You could do this, which has the virtue of simplicity:

>>>  class C(object):
    def __init__(self, **kwargs):
        self.__dict__ = dict(kwargs)

This leaves it up to whatever code creates an instance of C to decide what the instance's attributes will be after construction, e.g.:

>>> c = C(a='a', b='b', c='c')
>>> c.a, c.b, c.c
('a', 'b', 'c')

If you want all C objects to have a, b, and c attributes, this approach won't be useful.

(BTW, this pattern comes from Guido his own bad self, as a general solution to the problem of defining enums in Python. Create a class like the above called Enum, and then you can write code like Colors = Enum(Red=0, Green=1, Blue=2), and henceforth use Colors.Red, Colors.Green, and Colors.Blue.)

It's a worthwhile exercise to figure out what kinds of problems you could have if you set self.__dict__ to kwargs instead of dict(kwargs).

Robert Rossney
  • 87,288
  • 24
  • 136
  • 211
  • 2
    Can you add a link to Guido's discussion of this pattern and enums? – Air Apr 12 '16 at 17:23
  • This should be the top solution, specifically for its elegance and high probability of successful implementation. It functions correctly, is unlikely to behave unexpectedly, and takes only one---at most two---lines. – TheLoneDeranger Oct 15 '18 at 16:14
4

Mod for @pcperini's answer:

>>> class SomeClass():
        def __init__(self, a, b=1, c=2):
            for name,value in vars().items():
                if name != 'self':
                    setattr(self,name,value)

>>> s = SomeClass(7,8)
>>> print s.a,s.b,s.c
7 8 2
Seth Robertson
  • 27,856
  • 5
  • 55
  • 52
Jonathan
  • 84,911
  • 94
  • 244
  • 345
  • @ThiefMaster - removed the list comprehension. (you could give people a chance to correct their mistakes before taking action, guess this is true here as it is in life) – Jonathan Jan 02 '12 at 07:00
  • 1
    Votes can be changed for an indefinite period after the related answer/question has been edited ;) So there's nothing to say against downvoting+commenting and later removing the downvote and/or upvoting. – ThiefMaster Jan 02 '12 at 11:51
  • List comprehension version of the above: `[ setattr(self, var[0], var[1]) for var in vars().items() if var[0] != "self" ]` – Seth Robertson Jul 01 '20 at 13:17
3

Your specific case could also be handled with a namedtuple:

>>> from collections import namedtuple
>>> SomeClass = namedtuple("SomeClass", "a b c")
>>> sc = SomeClass(1, "x", 200)
>>> print sc
SomeClass(a=1, b='x', c=200)
>>> print sc.a, sc.b, sc.c
1 x 200
PaulMcG
  • 56,194
  • 14
  • 81
  • 122
1

I solved it for myself using locals() and __dict__:

>>> class Test:
...     def __init__(self, a, b, c):
...         l = locals()
...         for key in l:
...             self.__dict__[key] = l[key]
... 
>>> t = Test(1, 2, 3)
>>> t.a
1
>>> 
user3638162
  • 411
  • 1
  • 3
  • 10
1

You can do it via setattr(), like:

[setattr(self, key, value) for key, value in kwargs.items()]

Is not very beautiful, but can save some space :)

So, you'll get:

  kwargs = { 'd':1, 'e': 2, 'z': 3, }

  class P():
     def __init__(self, **kwargs):
        [setattr(self, key, value) for key, value in kwargs.items()]

  x = P(**kwargs)
  dir(x)
  ['__doc__', '__init__', '__module__', 'd', 'e', 'z']
Pavel Shvedov
  • 1,226
  • 10
  • 8
  • True, but then I can't define the function's argument list, which in most cases is preferred – Jonathan Dec 30 '11 at 19:14
  • You could define the class' attribute list by using a new-style class with `__slots__`. – Karl Knechtel Dec 30 '11 at 19:16
  • 3
    Why use a list comprehension rather than a loop? `for key, value in kwargs.items(): setattr(self, key, value)` is slightly shorter and yet more straightforward. I'd use iteritems() rather than items() in Py<3. –  Dec 30 '11 at 19:27
  • 2
    And for brevity, `for a in kwargs.items(): setattr(self, *a)` –  Dec 30 '11 at 19:28
  • 2
    -1 for using a list comprehension as a shortcut for a for-loop, this is not a good style and should not be promoted to new learners. (Creates a throwaway list of `None`s which are the return values from all the setattr calls, for no purpose.) – PaulMcG Dec 30 '11 at 23:08
  • 1
    -1 too. For me it's uglier than simply assigning and you also loose which are the accepted attributes (added an answer explaining how to use a Bunch class which I think it's better). – Fabio Zadrozny Dec 31 '11 at 23:14
1

Decorator magic!!

>>> class SomeClass():
        @ArgsToSelf
        def __init__(a, b=1, c=2, d=4, e=5):
            pass

>>> s=SomeClass(6,b=7,d=8)
>>> print s.a,s.b,s.c,s.d,s.e
6 7 2 8 5

while defining:

>>> import inspect
>>> def ArgsToSelf(f):
    def act(self, *args, **kwargs):
        arg_names,_,_,defaults = inspect.getargspec(f)
        defaults=list(defaults)
        for arg in args:
            setattr(self, arg_names.pop(0),arg)
        for arg_name,arg in kwargs.iteritems():
            setattr(self, arg_name,arg)
            defaults.pop(arg_names.index(arg_name))
            arg_names.remove(arg_name)
        for arg_name,arg in zip(arg_names,defaults):
            setattr(self, arg_name,arg)
        return f(*args, **kwargs)
    return act

Of course you could define this decorator once and use it throughout your project.
Also, This decorator works on any object function, not only __init__.

Jonathan
  • 84,911
  • 94
  • 244
  • 345
  • python probably already has code somewhere that matches `args,kwargs,defaults` into `locals()` or `vars()`, so I could save on implementing it again in this decorator - I'm open for suggestions... – Jonathan Dec 31 '11 at 10:57
  • btw, I believe that in most cases @senderle is right, however under certain circumstances this approach could be very convenient. A language construct could be even more elegant than this decorator, and also PyDev could support it thus not losing `Ctrl+space` functionality. Alas... – Jonathan Dec 31 '11 at 11:00
1

For that simple use-case I must say I like putting things explicitly (using the Ctrl+1 from PyDev), but sometimes I also end up using a bunch implementation, but with a class where the accepted attributes are created from attributes pre-declared in the class, so that I know what's expected (and I like it more than a namedtuple as I find it more readable -- and it won't confuse static code analysis or code-completion).

I've put on a recipe for it at: http://code.activestate.com/recipes/577999-bunch-class-created-from-attributes-in-class/

The basic idea is that you declare your class as a subclass of Bunch and it'll create those attributes in the instance (either from default or from values passed in the constructor):

class Point(Bunch):
    x = 0
    y = 0

p0 = Point()
assert p0.x == 0
assert p0.y == 0

p1 = Point(x=10, y=20)
assert p1.x == 10
assert p1.y == 20

Also, Alex Martelli also provided a bunch implementation: http://code.activestate.com/recipes/52308-the-simple-but-handy-collector-of-a-bunch-of-named/ with the idea of updating the instance from the arguments, but that'll confuse static code-analysis (and IMO can make things harder to follow) so, I'd only use that approach for an instance that's created locally and thrown away inside that same scope without passing it anywhere else).

Fabio Zadrozny
  • 23,566
  • 4
  • 60
  • 73
  • Nice, although this solves specifically the `__init__` case, whereas the decorator is more generic in this sense – Jonathan Jan 01 '12 at 10:05
  • True, but I think that pattern is only common in the constructor and setter methods (but as setter methods usually have only 1 argument, changing the assign line for the decorator line only makes the come more unreadable to me). – Fabio Zadrozny Jan 01 '12 at 14:04
0

One of the problems with @user3638162's answer is that locals() contain the 'self' variable. Hence, you end up with an extra self.self. If one doesn't mind the extra self, that solution can simply be

class X:
    def __init__(self, a, b, c):
        self.__dict__.update(locals())

x = X(1, 2, 3)
print(x.a, x.__dict__)

The self can be removed after construction by del self.__dict__['self']

Alternatively, one can remove the self during construction using dictionary comprehensions introduced in Python3

class X:
    def __init__(self, a, b, c):
        self.__dict__.update(l for l in locals().items() if l[0] != 'self')

x = X(1, 2, 3)
print(x.a, x.__dict__)
rahul
  • 709
  • 7
  • 19
0

Disclaimer

Do not use this: I was simply trying to create the answer closest to OPs initial intentions. As pointed out in comments, this relies on entirely undefined behavior, and explicitly prohibited modifications of the symbol table.

It does work though, and has been tested under extremely basic circumstances.

Solution

class SomeClass():
    def __init__(self, a, b, c):
        vars(self).update(dict((k,v) for k,v in vars().iteritems() if (k != 'self')))

sc = SomeClass(1, 2, 3)
# sc.a == 1
# sc.b == 2
# sc.c == 3

Using the vars() built-in function, this snippet iterates through all of the variables available in the __init__ method (which should, at this point, just be self, a, b, and c) and set's self's variables equal to the same, obviously ignoring the argument-reference to self (because self.self seemed like a poor decision.)

Community
  • 1
  • 1
Patrick Perini
  • 22,041
  • 11
  • 55
  • 88
  • 2
    http://docs.python.org/library/functions.html#vars explicitly disallows modifying vars(). –  Dec 30 '11 at 19:18
  • 1
    disclaimer updated. i'm hesitant to take it down though, just because i feel it's a pretty cool answer :) – Patrick Perini Dec 30 '11 at 19:22