(One of my summaries of the one-day Pycon NL conference in Utrecht, NL).
Brett is the writer of the effective python book.
As an example, he picked a calculator that can add and multiply integers. The numbers, the operators: everything is an object in his example. You can do it with one big function, object oriented or with dynamic dispatch.
One big function: just a big old recursive function with a lot of if/else. Just something to get started. Once you add more operators (“power of”, “square root”), the if/else quickly gets out of hand and becomes much to long and unreadable.
And lets say you don’t only want to do the calculation, but you only want to have a nicely printed version of the calculation. Suddenly your only choice is to copy/paste the entire big function and change the calculation to print statements. And then you need to keep the two in sync…
Second try: object oriented programming. Polymorphism. A generic Operator
class
with a calculate()
method. Then subclasses called Add
, Multiply
, etc.
Python does a magical thing: you can just call calculate()
on whatever
operator. Python handles part of the if/else for you. It calls the right calculate
method. That’s one of the nice things about OO programming.
Also nice: adding the printing functionality means adding a print method on the various classes. The rest of the code can remain the same. Nice.
But… it is easy to imagine that the objects grow and grow and grow. Instead of a “god function” you get a “god object” with tens of methods… An object does a lot of things, so the one object probably needs multiple libraries. If all the formatting-related code would have been in one file, only that file would have needed the formatting library. With OO code, you tend to have a file per object, so every file needs every library and functionality is split over multiple files.
OO advantages: behavior next to data, easy to add more methods, avoids dispatching duplication. Drawbacks: behaviour spread over classes, scattered dependencies, dispatching is magical.
Dynamic dispatching. Python has from functools import singledispatch
, since a
long time. It isn’t widely known. Only one person at the conference had used it in
anger! You can use it like this:
@singledispatch
def my_print(value):
print("not implemented")
@my_print.register(int)
def _(value):
print(f"Integer: {value}")
@my_print.register(str)
...
So… you can have a function with multiple implementations for specific types. You can
have pretty dumb classes (dataclasses?) and register a calculate()
method for each
type. That way similar behavior can be kept together in the same file.
So… you get a lot of the benefits of OO programming without lots of the drawbacks.
Dynamic dispatch advantages: simple classes, functionality is grouped, isolated dependencies, code organized on correct axis. Downsides: data and behavior separate, less encapsulation, a bit more friction when adding new classes. When you add a class, you have to remember which functions you need to add in which files.
Another drawback is that the functions tend to poke around a bit in the objects in a way that’s not really clean, but… we’re using python so objects aren’t as sacrosanct as in other languages and we’re dependent upon our own good behavior anyway :-)
His summary:
One big function: fine to start, but quickly deteriorates.
OO: good when classes share behaviurs and when larger systems are strongly interconnected.
Dynamic dispatch: good when data types are shared, but larger systems are indipendent.
Mixing OO and dynamic dispatch: good when both cohesion in the small and system decoupling in the large are needed.
My name is Reinout van Rees and I program in Python, I live in the Netherlands, I cycle recumbent bikes and I have a model railway.
Most of my website content is in my weblog. You can keep up to date by subscribing to the automatic feeds (for instance with Google reader):