Python class design: explicit keyword arguments vs. **kwargs vs. @property

I'm sure there are many different schools of thought on this, by here's how I've usually thought about it:

Explicit Keyword Arguments

Pros

  • Simple, less code
  • Very explicit, clear what attributes you can pass to the class

Cons

  • Can get very unwieldy as you mention when you have LOTs of things to pass in

Prognosis

This should usually be your method of first attack. If you find however that your list of things you are passing in is getting too long, it is likely pointing to more of a structural problem with the code. Do some of these things you are passing in share any common ground? Could you encapsulate that in a separate object? Sometimes I've used config objects for this and then you go from passing in a gazillion args to passing in 1 or 2

Using **kwargs

Pros

  • Seamlessly modify or transform arguments before passing it to a wrapped system
  • Great when you want to make a variable number of arguments look like part of the api, e.g. if you have a list or dictionary
  • Avoid endlessly long and hard to maintain passthrough definitions to a lower level system,

e.g.

def do_it(a, b, thing=None, zip=2, zap=100, zimmer='okay', zammer=True):
    # do some stuff with a and b
    # ...
    get_er_done(abcombo, thing=thing, zip=zip, zap=zap, zimmer=zimmer, zammer=zammer)

Instead becomes:

def do_it(a, b, **kwargs):
    # do some stuff with a and b
    # ...
    get_er_done(abcombo, **kwargs)

Much cleaner in cases like this, and can see get_er_done for the full signature, although good docstrings can also just list all the arguments as if they were real arguments accepted by do_it

Cons

  • Makes it less readable and explicit what the arguments are in cases where it is not a more or less simple passthrough
  • Can really easily hide bugs and obfuscate things for maintainers if you are not careful

Prognosis

The *args and **kwargs syntax is super useful, but also can be super dangerous and hard to maintain as you lose the explicit nature of what arguments you can pass in. I usually like to use these in situations when I have a method that basically is just a wrapper around another method or system and you want to just pass things through without defining everything again, or in interesting cases where the arguments need to be pre-filtered or made more dynamic, etc. If you are just using it to hide the fact that you have tons and tons of arguments and keyword arguments, **kwargs will probably just exacerbate the problem by making your code even more unwieldy and arcane.

Using Properties

Pros

  • Very explicit
  • Provides a great way of creating objects when they are somehow still "valid" when not all parameters are you known and passing around half-formed objects through a pipeline to slowly populate args. Also for attributes that don't need to be set, but could be, it sometimes provides a clean way of pairing down your __init__'s
  • Are great when you want to present a simple interface of attributes, e.g. for an api, but under the hood are doing more complicated cooler things like maintaining caches, or other fun stuff

Cons

  • A lot more verbose, more code to maintain
  • Counterpoint to above, can introduce danger in allowing invalid objects with some properties not yet fully initialized to be generated when they should never be allowed to exist

Prognosis

I actually really like taking advantage of the getter and setter properties, especially when I am doing tricky stuff with private versions of those attributes that I don't want to expose. It can also be good for config objects and other things and is nice and explicit, which I like. However, if I am initializing an object where I don't want to allow half-formed ones to be walking around and they are serving no purpose, it's still better to just go with explicit argument and keyword arguments.

TL;DR

**kwargs and properties have nice specific use cases, but just stick to explicit keyword arguments whenever practical/possible. If there are too many instance variables, consider breaking up your class into hierarchical container objects.


Without really knowing the particulars of your situation, the classic answer is this: if your class initializer requires a whole bunch of arguments, then it is probably doing too much, and it should be factored into several classes.

Take a Car class defined as such:

class Car:
    def __init__(self, tire_size, tire_tread, tire_age, paint_color, 
                 paint_condition, engine_size, engine_horsepower):
        self.tire_size = tire_size
        self.tire_tread = tire_tread
        # ...
        self.engine_horsepower = engine_horsepower

Clearly a better approach would be to define Engine, Tire, and Paint classes (or namedtuples) and pass instances of these into Car():

class Car:
    def __init__(self, tire, paint, engine):
        self.tire = tire
        self.paint = paint
        self.engine = engine

If something is required to make an instance of a class, for example, radius in your Circle class, it should be a required argument to __init__ (or factored into a smaller class which is passed into __init__, or set by an alternative constructor). The reason is this: IDEs, automatic documentation generators, code autocompleters, linters, and the like can read a method's argument list. If it's just **kwargs, there's no information there. But if it has the names of the arguments you expect, then these tools can do their work.

Now, properties are pretty cool, but I'd hesitate to use them until necessary (and you'll know when they are necessary). Leave your attributes as they are and allow people to access them directly. If they shouldn't be set or changed, document it.

Lastly, if you really must have a whole bunch of arguments, but don't want to write a bunch of assignments in your __init__, you might be interested in Alex Martelli's answer to a related question.


Passing arguments to the __init__ is usually the best practice like in any Object Oriented programming language. In your example, setters/getters would allow the object to be in this weird state where it doesn't have any attribute yet.

Specifying the arguments, or using **kwargs depends on the situation. Here's a good rule of thumb:

  1. If you have many arguments, **kwargs is a good solution, since it avoids code like this:
def __init__(first, second, third, fourth, fifth, sixth, seventh,
             ninth, tenth, eleventh, twelfth, thirteenth, fourteenth,
             ...
             )
  1. If you're heavily using inheritance. **kwargs is the best solution:
class Parent:
    def __init__(self, many, arguments, here):
        self.many = many
        self.arguments = arguments
        self.here = here

class Child(Parent):
    def __init__(self, **kwargs):
        self.extra = kwargs.pop('extra')
        super().__init__(**kwargs)

avoids writing:

class Child:
    def __init__(self, many, arguments, here, extra):
        self.extra = extra
        super().__init__(many, arguments, here)

For all other cases, specifying the arguments is better since it allows developers to use both positional and named arguments, like this:

class Point:
    def __init__(self, x, y):
       self.x = x
       self.y = y

Can be instantiated by Point(1, 2) or Point(x=1, y=2).

For general knowledge, you can see how namedtuple does it and use it.

Tags:

Python

Oop

Class