The Perils of Default Arguments in Python
1 point by going_north 3 months ago | 1 comment- zahlman 3 months agoYes, this is the subject of one of the "classic" posts of OG Stack Overflow (https://stackoverflow.com/questions/1132941). And in fact it commonly gets deliberately abused (https://stackoverflow.com/questions/3431676) despite better tools being available for the intended effect (https://stackoverflow.com/questions/277922).
But it's good to have more writeups, I suppose. And it's good to point out that the initializers of a `dataclass` will show the same kind of early binding, and how to use `default_factory` instead. (It's not immediately obvious that this is related to the issue with function arguments, but it is.)
> That is a very roundabout way of saying that if you do this... the default argument value for base_url will be "eaj.no", and not "example.com".
This isn't a good example. Even in a hypothetical alternate Python where `base_url` had its default value read at call time, `some_function` isn't called anywhere, nor does it modify the global `default_url`, so there's no reason why the behaviour of `get_post_url` would change.
> Notably, we can also pass a variable as the default argument value.
No; as already established, a value is used - the value that results from evaluating the expression `default_url` (which happens to consist of just a variable name). Also, default argument values aren't "passed" - that's the point.
> In short, default arguments should never be used for values that are required to change between function calls.
More accurately, subsuming the previous section: they should not be used for values that must be determined at the time the function is called. Because that isn't what happens.
> Use None as a sentinel value and conditionally create the mutable default value in the function body instead.
This is the standard advice, but I hate it. "Special cases aren't special enough to break the rules"; `None` is overly magical in a lot of Python code. Besides, writing code that modifies the passed-in value and then also `return`s a value isn't Pythonic - https://en.wikipedia.org/wiki/Command%E2%80%93query_separati... is a key part of the design of the standard library and of several builtin methods. Maybe you expect the caller to use a temporary for every call, but maybe the caller didn't understand that.
A lot of the time, a better approach is to just use an equivalent immutable value.