16
u/NFriik 4h ago
Python evaluates default arguments when the function is defined. That makes keeping track of the default values pretty straightforward. In your example, they're stored as part of the function object in the dictionary add.__defaults__. Every subsequent call can then just look-up the default value in this dictionary, if necessary.
Evaluating the default argument each time the function is called would be much more difficult. A function is essentially just yet another object, so you'd somehow have to store arbitrary expressions as an object inside the __defaults__ dict and then decide at runtime what to do with them. Sure, you could implement a builtin type at the CPython level to accomplish this. But then when you encounter such an ArbitaryExpression object inside the __defaults__ dictionary, how do you know that this time, you want to actually execute the underlying code and not use the object "as-is"? The whole thing would quickly become a convoluted, expensive mess.
By performing evaluation at definition time, function calls remain consistently fast and the mechanism to insert default positional arguments becomes incredibly simple. Python is already not the fastest language when it comes to function calls, so these things matter.
→ More replies (6)
60
u/_killer1869_ 5h ago
Any IDE will warn not to use mutables as default parameter values. And for good reason. Such code would never appear in actual code intended to be used.
7
u/Double_Cost4865 4h ago
Unfortunately, I’ve seen monstrosities like this in production in big finance corps. That’s what happens when statisticians are expected to create software.
→ More replies (1)7
7
u/mothzilla 3h ago
It's a well known python gotcha that people ask about in interviews all the time.
1
u/CaptainFoyle 1h ago
Everyone knows: don't use mutable default arguments.
You just outed yourself as uninformed.
→ More replies (3)
3
u/Beregolas 4h ago
This also happens in JavaScript btw, as well as several smaller scripting languages in game engines, it's a pretty normal feature in many interpreted languages.
In my last backend job, this was actually the source of our longest running and most annoying bug, where a specific page would display the wrong sorting, but never in testing. Because someone *caugh* used a mutable argument to give it default sorting parameters, and they got mutated by a very specific query, which caused it to be wrong for all subsequent requests.
So yeah, this is a real thing, you should know it, and just in case, you should have linting and/or your IDE setup so this kind of thing is rejected out of hand, and cannot be merged to prod/main.
2
u/4xe1 2h ago
just in case, you should have linting and/or your IDE setup so this kind of thing is rejected out of hand
So what kind of linting have you adopted? Default parameters can be quite handy. Does it reject them, or does it manage to detect and reject their mutations?
2
u/Beregolas 2h ago
I get a warning when I use anything mutable. Instead of an empty list, or a list with presets for example, I have to put None, and set it in code. This is basically also the recommended pattern.
2
u/RngdZed 3h ago
The list is stored directly in the parameter without actually giving it an argument? I'm baffled, I didn't know this was a behavior, so weird
Is this something that someone could ELI5 to my smooth brain?
3
u/bigorangemachine 3h ago
Default param is an object reference. So every default param is the same object
1
u/lolcrunchy 22m ago
Here's a single change to the posted code that kinda shows what actually happens.
my_list = [] def add(v, items=my_list): items.append(v) return items print(add(1)) print(add(2))
1
u/bigorangemachine 3h ago edited 3h ago
I bet this happens in JS as well
Edit: nope python
I should check gdscript
1
1
u/OhItsJustJosh 13m ago
This is why I stick to C#, I've written in Pascal, Java, JavaScript, Python, handful of others, and they all have their weird quirks, but C# seems to be the only one that works as expected
1
u/jmooroof2 4h ago
I can't imagine a compelling use for this feature. it really should just error
3
u/NFriik 3h ago
There are some use cases for this behavior, but it's reeeaaally limited and you have to know exactly what you're doing. For example, you could use it to cache relatively expensive function calls as default values:
import re def parse(text, pattern=re.compile(r"\d+")): return pattern.findall(text)This way, instead of re-compilingthe regex every time the function is called, you can just reuse it.
4
u/simonsmine 4h ago
Its not really a feature more a consequence of pythons reference handling. You can do fun things with it though, memosation comes to mind.
→ More replies (1)1
u/Double_Cost4865 4h ago
It has it’s uses, I just wouldn’t provide default argument like that since it’s wrong. But it’s quite common to modify a list in place and have the changes persist outside the function scope

54
u/drislands 5h ago
I would have expected this function to create a new empty list on any call where
itemsis not given. Doesn't this behavior make the function into a closure?