57

In Python, say I have some class, Circle, that inherits from Shape. Shape needs x- and y-coordinates, and, in addition, Circle needs a radius. I want to be able to initialize Circle by doing something like,

c = Circle(x=1., y=5., r=3.)

Circle inherits from shape, so I need to use named arguments to __init__, because different classes require different constructors. I could manually set x, y, and r.

class Shape(object):
    def __init__(self, **kwargs):
        self.x = kwargs['x']
        self.y = kwargs['y']

class Circle(Shape):
    def __init__(self, **kwargs):
        super(Circle, self).__init__(**kwargs)
        self.r = kwargs['r']

or, I could have the attributes of my Circle set automatically using self.__dict__.update(kwargs)

class Shape(object):
    def __init__(self, **kwargs):
        self.__dict__.update(**kwargs)

class Circle(Shape):
    def __init__(self, **kwargs):
        super(Circle, self).__init__(**kwargs)

The advantage of this is that there's less code and I don't need to maintain boilerplate like self.foo = kwargs['foo']. The disadvantage is that it isn't obvious which arguments are needed for Circle. Is this considered a cheat or is this good style (as long as the interface to Circle is well-documented)?


Thanks, everyone, for your thoughtful responses. The self.__dict__.update(**kwargs) hack has been useful for me in experimenting with organizing my code, but I'll make sure that I replace that with properly passing arguments explicitly and doing clear error checking in production code.

Simeon Visser
  • 118,920
  • 18
  • 185
  • 180
Alex Szatmary
  • 3,431
  • 3
  • 21
  • 30
  • I think this would allow a caller to overload methods .... but if you are teh only person using the code and are not concerned about malicious behavior its probably alright. – Joran Beasley Mar 15 '12 at 21:26
  • @JoranBeasley - Yes, but if you've done that you're essentially breaking the rules, and it's your own fault. – Chris Lutz Mar 15 '12 at 21:31
  • 1
    Also see [Raymond Hettinger's answer to "Pythonic: use of \_\_dict__ in the function self.__init__ of a class"](http://stackoverflow.com/a/9205029/2705757) – AXO Apr 22 '17 at 16:05

4 Answers4

33
class Shape(object):
    def __init__(self, x=None, y=None):
        self.x = x
        self.y = y

class Circle(Shape):
    def __init__(self, r=None, **kwargs):
        super(Circle, self).__init__(**kwargs)
        self.r = r

And this is it. Don't use **kwargs when you don't really need them.

Is this considered a cheat or is this good style (as long as the interface to Circle is well-documented)?

When you have a choice between writing a simple, understandable code and headache code + nice docstrings, you actually don't have any choices, you just go and write simple, self-documented code:)

Roman Bodnarchuk
  • 29,461
  • 12
  • 59
  • 75
  • there are plenty of perfectly reasonable times to use **whatever especially when you are overloading other classes – Joran Beasley Mar 15 '12 at 21:27
  • @StevenRumbalski Yeah, I had a filling that something is missed:) – Roman Bodnarchuk Mar 15 '12 at 21:36
  • 2
    For this solution, be aware that all classes in this chain of inheritance will not have access to the keyword values that are stripped from `**kwargs` in this way (in the child classes). For example, Shape cannot define an `r` parameter and expect to get it, without modifying the `__init__` method of Circle. – Casey Kuball Mar 15 '12 at 23:10
  • I prefer this solution because it keeps me from having to mention x explicitly in every `__init__()` all the way down the class hierarchy, while ensuring that the right arguments are present at the right level. – Alex Szatmary Mar 16 '12 at 04:46
19

I would say that the first method is definitely preferable, because explicit is better than implicit.

Consider what would happen if you made a typo when initializing a Circle, something like Circle(x=1., y=5., rr=3.). You want to see this error immediately, which would not happen with __dict__.update(kwargs).

Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
19

If you wish to assign automatically, I suggest the following approach:

def __init__(self, **kwargs):
    for key, value in kwargs.iteritems():
        setattr(self, key, value)

which, in terms of style, is somewhere in between writing it explicitly and hacking it yourself using self.__dict__.

Simeon Visser
  • 118,920
  • 18
  • 185
  • 180
  • 1
    I'd much rather see `self.__dict__.update(kwargs)` than this (probably less efficient and) longer way to achieve the same result. – Chris Lutz Mar 15 '12 at 21:47
  • 6
    This would have the advantage of working with descriptors. Working directly with `__dict__` is a bad idea for that reason alone. – Chris Morgan Mar 15 '12 at 21:50
  • 3
    Could you help me understand why this works better with descriptors? – Eli Rose May 29 '14 at 04:14
  • 3
    My understanding, after reading https://docs.python.org/3/howto/descriptor.html , is that Chris Morgan (~2) is promoting Simeon's answer because it would work better with classes BASED on your class which DO over-ride the default get/set methods and thus become data descriptors. Using setattr would be compatible with their over-rides, while using the __dict__ directly would bypass any such overrides. – Michael J. Evans Oct 08 '16 at 03:53
4

If you wanted it to be more obvious you could have Circle.__init__ perform some sanity checks on the arguments. Presumably you'd check to make sure all the arguments are there, and possibly raise errors for meaningless arguments.

You could probably even make a decorator or helper function in Shape to do this for you. Something like this:

class Circle(Shape):
    def __init__(self, **kwargs):
        self.check(kwargs, 'x', 'y', 'r')
        super(Circle, self).__init__(**kwargs)

.check would be implemented in Shape and essentially just verifies that all the arguments are in kwargs, and possibly that no extra ones are (sorry, no code for that one - you can figure it out on your own). You could even have subclasses overload it to check for optional arguments, which you may want to handle differently than other arguments (i.e. give them a default value that wouldn't otherwise be assigned in Shape.__init__.

Otherwise, if you document your interface, and it works the way it's documented, it's always alright. Anything else you do to make it work the way we "expect" it to (throwing exceptions for incorrect arguments) is a bonus.

Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
  • How do you know that "`.check` is implemented in `Shape`". Are you the OP's teacher? – Steven Rumbalski Mar 15 '12 at 21:39
  • 2
    @StevenRumbalski - I'd be the youngest computer science teacher ever if I was. What I meant was that the OP could implement `Shape.check` and then use it in all the subclasses. I'll clarify that. – Chris Lutz Mar 15 '12 at 21:44