I've recently come across an interesting idea in the Python discussion forum. What the guy proposes is:
The idea aims to provide a concise and natural way to skip passing an argument so that the function default applies, without duplicating calls or relying on boilerplate patterns.
Using for example a syntax like this:
def fetch_data(user_id: int, timeout: int = 10) -> None:
"""
Hypothetical API call.
timeout: network timeout in seconds, defaults to 10
"""
timeout: int | None = ... # Could be an int or None.
user_id: int = 42
fetch_data(
user_id,
timeout = timeout if timeout # passes only if timeout is not None; or if timeout is truthy.
)
In the lack of this feature, we could opt for providing the default value at the callsite:
fetch_data(
user_id,
timeout = timeout if timeout else 10 # we repeat here the default value
)
This is repetitive, as the that default value is already part of the function signature, and furthermore, it becomes incorrect if the function signature changes. It's this point that feels very interesting, as I had never thought much about how we should understand default parameters from a design point of view. The idea for me is that default parameters should be considered as an implementation detail, not as part of the contract provided by the function. So if the function decides to change the values for its default parameters our client code should continue to work, as it should not care about those values. So, if we want to pass a certain value, and it happens to be the current default value, we should pass it anyway, as that default could change in the future. On the other hand, if we don't care about that parameter and just want the function to use its default value, we should not pass it explicitly (that is what we're doing in the previous code and we should not). I've further discussed this with a GPT:
Default parameter values are generally considered implementation details, not part of the contract. The contract is:
“If you omit this argument, the function will pick a value for you.”
But what that value is should not be relied upon unless explicitly documented as part of the API guarantee.
If your calling code cares about the value, it should pass it explicitly, even if it happens to match the current default.
That way, if the default changes later (which is common in evolving APIs), your code still behaves as intended.
Best Practice Summary:
- Treat defaults as convenience, not as a contract.
- Explicit beats implicit when correctness matters.
- If you rely on a specific value → pass it explicitly.
- If you’re okay with whatever the function decides → omit the argument.
Having said this, the idea proposed in the forum, having some syntax that easily allowed us to conditionally choose between providing a value or saying: "use your default" makes pretty much sense. And it makes sense to wonder if any other languages support this feature. I was not aware of any, but was assuming (based on how an extremely powerful and expressive language it is) that maybe Ruby would support it, but no, it does not. But there's another brilliant and lovely language that happens to support it, JavaScript, thanks to a "feature" that normally is considered more a problem than a benefit, the confusing coexistence between null and undefined.
In JavaScript when we don't explicitly provide a parameter to a function, it takes the undefined value. If that parameter was defined with a default value, it then will use that default rather than undefined (this won't be the case if we pass over the null value). So this means that if we want to say "use your default" we can just pass it over the undefined value
function sayMessage(person: string, msg: string = "Bonjour") {
console.log(`${person}: ${msg}`);
}
const isEnglish = false;
sayMessage("Xuan", isEnglish ? "Hi" : undefined);
// "Bonjour"
This idea of a syntax that allows conditionally omitting a parameter and forcing the default is indeed related to a previous idea that I discussed in this post, conditional collection literals. And similiar to the workaround that I show in that post, I've come up with a simple function "invoke_with_use_default" (sorry, I can't come with a nice name for it) that can help us to try to emulate this missing feature.
def invoke_with_use_default(fn: Callable, *args, **kwargs) -> Any:
args = [arg for arg in args if arg is not USE_DEFAULT]
kwargs = {key:value for key, value in kwargs.items() if value is not USE_DEFAULT}
return fn(*args, **kwargs)
def generate_story(main_char: str, year: int, city: str = "Paris", duration: int = 5) -> str:
return f"This is an adventure of {main_char} in {city} in year {year} that lasts for {duration} days"
in_asturies = False
is_short = False
print(invoke_with_use_default(generate_story,
"Francois",
2025,
"Xixon" if in_asturies else USE_DEFAULT,
duration=(10 if is_short else USE_DEFAULT),
))
# This is an adventure of Francois in Paris in year 2025 that lasts for 5 days
in_asturies = True
print(invoke_with_use_default(generate_story,
"Francois",
2025,
"Xixon" if in_asturies else USE_DEFAULT,
duration=(10 if is_short else USE_DEFAULT),
))
# This is an adventure of Francois in Xixon in year 2025 that lasts for 5 days
We could also think of having a decorator function "enable_use_defaults" that enables a function to be invoked with a USE_DEFAULT directive.
def enable_use_default(fn: Callable) -> Callable:
@wraps(fn)
def use_default_enabled(*args, **kwargs):
args = [arg for arg in args if arg is not USE_DEFAULT]
kwargs = {key:value for key, value in kwargs.items() if value is not USE_DEFAULT}
return fn(*args, **kwargs)
return use_default_enabled
@enable_use_default
def generate_story(main_char: str, year: int, city: str = "Paris", duration: int = 5) -> str:
return f"This is an adventure of {main_char} in {city} in year {year} that lasts for {duration} days"
in_asturies = False
is_short = False
print(generate_story(
"Francois",
2025,
"Xixon" if in_asturies else USE_DEFAULT,
duration=(10 if is_short else USE_DEFAULT),
))