Fixing Python Default Parameters
Contents
Important: Pyodide takes time to initialize. Initialization completion is indicated by a red border around Run all button.
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
again
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
again
A problem with this is that it messes with the function signature when using type hints. However, one can work around it by using a wrapper function.
Wit this, we provide default parameters to @fix_params
instead with same
name but wrapped in a lambda, which overwrites the provided default value.
using it.
another.
The full code is here.
Artifacts
The runnable Python source for this notebook is available here.