Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

python

oop

class

Is there a generally accepted best practice for creating a class whose instances will have many (non-defaultable) variables?

For example, by explicit arguments:

class Circle(object):
    def __init__(self,x,y,radius):
        self.x = x
        self.y = y
        self.radius = radius

using **kwargs:

class Circle(object):
    def __init__(self, **kwargs):
        if 'x' in kwargs:
            self.x = kwargs['x']
        if 'y' in kwargs:
            self.y = kwargs['y']
        if 'radius' in kwargs:
            self.radius = kwargs['radius']

or using properties:

class Circle(object):
    def __init__(self):
        pass

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @property
    def y(self):
        return self._y

    @y.setter
    def y(self, value):
        self._y = value

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        self._radius = value

For classes which implement a small number of instance variables (like the example above), it seems like the natural solution is to use explicit arguments, but this approach quickly becomes unruly as the number of variables grows. Is there a preferred approach when the number of instance variables grows lengthy?

like image 518
andykee Avatar asked Jun 17 '15 04:06

andykee


1 Answers

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.

like image 59
lemonhead Avatar answered Oct 07 '22 19:10

lemonhead