Skip to content

Parameter Object in Python

It's no news that Design Patterns have a very important role in Object-Oriented software. They are very good at communicating design intent and establishing idea "skeletons" for solving computational problems. It's not rare that you may find an Abstract Factory in Java, a Factory Method or a Builder in Python.

Some patterns however can be easily overlooked; This is the case of the Parameter Object pattern. It's in fact a very simple pattern: the idea is that you create an object that holds parameters that you can pass around as arguments to functions, instead of having these parameters spread along the function headers. Its main purpose is to facilitate the addition or removal of function parameters, but it turns out that this pattern is more useful than it looks.

The problem and the obvious solution

Python by itself is already pretty good at being friendly to developers and allowing them to pass arguments as keyword parameters. The problem is when these parameters not only have to change on a certain function, but also on a bunch of other functions related to that. That's when the Parameter Object becomes immediately helpful: you can replace a number, or all, of the parameters by a Parameter Object, and then this object can be further extended in the future without any of those functions needing to change their headers.

Is that all you got?

On the surface that seems like a one-trick pony, but the pattern has another very useful application: storing state. And this has an even better use when it's passed around during complex data processing.

Throughout the years I developed a tendency to separate state from behavior in my classes, as I found this helps to reduce surprising behaviors and to better control logic paths. I tend to keep my behavior classes "stateless", or close to that, and the let them receive objects that represent state.

As projects grow in size, and the data processing parts become more and more complex, keeping state might become a burden; Using the Parameter Object here helps a lot, since you can have a single object containing a lot of the state being passed around through functions. For example, a state object like that might contain a number of dataframes which are used in complex logic distributed in different functions, but also contain the context of that data processing.

And this is yet another reason why this pattern can be so useful: context visibility. When debugging code, using debugging tools, it can become tedious to jump up and down the stack to find which values are being used when a certain error happens, for example; Using a state object passed around as a Parameter Object helps avoiding a number of these jumps by packing together the context when the breakpoint was reached.

Not only that, but the state object can also be a subject in the Observer pattern. Sometimes you need to implement behavior that only happens when the data reaches a certain specific state, and you can use the state object to notify its observers when this happens. For example, you can use this to notify observers of a connection pool, and then you could have an observer to send the connection counts to a service that collects system metrics, another that restarts some component in case the pool hits the limit, another that logs the number of connections etc.

So it can be used anywhere, right?

Wrong. A particularly tricky scenario to use this approach is on concurrent code, because of race conditions. One of the disadvantages of the pattern is that it may lead to functions knowing too much about the state, and as a result sometimes developers might want to change the state at a point that they shouldn't, and with concurrency involved this problem can become even bigger. In these cases, using locks might help, or not, depending on the circumstances.

Conclusion

Like other design patterns, and solutions in general, it's not a silver bullet, and should be used with good sense. But it's certainly a very useful design pattern, which I've been using far more than I expected.