1. 4.7.1. Default Argument Values


As the The Hitchhiker’s Guide to Python! notes, The way Python default parameters work is a constant source of annoyance and bugs. For example,

Here we are using it


As you can see, the default arguments appear to be something other than what you would normally expect. That is, it seems that they maintain state. What actually happens here is that, these values are produced when the function is defined rather than when the function is invoked. This makes them unusable for the intended purpose as actual default arguments. A common workaround is to define the argument value as None and check for that in code.

This pattern is however, unsatisfying, and really error prone. Even worse, it violates the principle of least surprise. That is, the official python tutorial says:

4.7.1. Default Argument Values

The most useful form is to specify a default value for one or more arguments. This creates a function that can be called with fewer arguments than it is defined to allow. They have to add the caveat “Important warning: The default value is evaluated only once.” which should have been a fairly large hint that this is not the expected behavior. For most part, the behavior is pretty sane, and you can often avoid the insane parts by simply not using them. However, default arguments are too convenient to leave aside. So what should we do? Thankfully, the dynamic nature of Python means that even when the language implementation is insane, you can bring back sanity by monkey patching it. In our case, you can write a function decorator that inspects the default arguments, and provides the correct behavior. Here is an implementation.

What this does is to inspect the default arguments and if it is a callable, it calls that, and provides the result as the actual default value during invocation. It means that we can now do this.

Here we are using it


A problem with this is that it messes with the function signature when using type hints. However, one may be able to work around it by using a wrapper function. The full code is here.