Python Patterns: Kwargs Helper Method

Writing usable, functioning code can be hard enough. Now imagine writing code that you need to make extensible enough that other developers can extend without simply copy-n-pasting your source code and making their own modifications. That can be rough. There are some patterns that you occasionally find in frameworks like Django, however, that I haven’t seen documented. This morning, I contributed a bugfix to werkzeug based on a pattern I’ve seen before. I’m calling it the kwargs helper method.

Motivation

You have a method that returns an object or the result of a function, both of which are variable. Through other parts of your code, other developers can change what your function will return. Examples of this include Django’s ListView.get_queryset, and Werkzeug’s Rule.empty (as of v0.10).

You need to allow other developers to control what gets passed into the objects and functions as they’re called. Without such a mechanism, developers are forced to override the entire method and in the worse case re-implement part of your code. I want you to stop that.

Example of Problem Code

Here’s a contrived example of the code in question.

1
2
3
4
5
6
class Sheep(object):
    def __init__(self, name=Dolly):
        self.name = name

    def clone(self):
        return type(self)(name=self.name)

Note the type(self) call here. That returns Sheep in this example, but returns whatever type the subclass is. So when we create a BionicSheep like the one below, we have a problem:

1
2
3
4
5
6
class BionicSheep(Sheep):
    def __init__(self, turbo_legs=None, **kwargs):
        self.turbo_legs = turbo_legs
        super(BionicSheep, self).__init__(**kwargs)

    # what do do about cloning?

At this point, BionicSheep is broken if you try to clone it. The clone method won’t pass in the turbo_legs value. You now have two options: copy-n-paste the whole clone method to remove Sheep.clone from the equation entirely or call super to get the result of Sheep.clone, then add your own values and duplicate the assignment in __init__. The latter option isn’t horrible in this case, but if __init__ provided different functionality based on that kwarg you would be forced to copy-n-paste clone and provide your own duplicate implementation.

Implementation

The solution is to provide a helper method that provides the kwargs outside of the actual function. I’m calling this pattern the kwargs helper method. This provides a granular hook for other developers to change the arguments that are provided without having to override the main method and possibly duplicate code.

You need to modify the Sheep.clone method to work like this to use this pattern:

1
2
3
4
5
6
7
8
9
class Sheep(object):
    def __init__(self, name=Dolly):
        self.name = name

    def clone(self):
        return type(self)(**self.get_clone_kwargs())

    def get_clone_kwargs(self):
        return {name: self.name}

Now you have a nice hook for providing your own custom kwargs in subclasses and nobody has to touch clone. Here’s an implementation of BionicSheep that works with the new code:

1
2
3
4
5
6
7
8
9
class BionicSheep(Sheep):
    def __init__(self, turbo_legs=None, **kwargs):
        self.turbo_legs = turbo_legs
        super(BionicSheep, self).__init__(**kwargs)

    def get_clone_kwargs(self):
        kwargs = super(BionicSheep, self).get_clone_kwargs()
        kwargs[turbo_legs] = self.turbo_legs
        return kwargs

Conclusion

I started by saying that writing functioning code is hard. Making sure your code is extensible for every other developer to use is ten times harder. Think not only about what each line of code in your codebase does, but also how it’s used and extended. I promise, some developer somewhere is going to want to change just about every line of your code. Be nice and make it easy on them.

Comments