Is it OK to have a mutable default argument if it's annotated as immutable?

109 Views Asked by At

It is generally, for good reason, considered unsafe to use mutable default arguments in python. On the other hand, it is quite annoying to always have to wrap everything in Optional and do the little unpacking dance at the start of the function.

In the situation when one want to allow passing **kwargs to a subfunction, it appears there is an alternative option:

def foo(
    x: int,
    subfunc_args: Sequence[Any] = (),
    subfunc_kwargs: Mapping[str, Any] = {},
) -> R:
    ...
    subfunc(*subfunc_args, **subfunc_kwargs)

Obviously, {} is a mutable default argument and hence considered unsafe. HOWEVER, since subfunc_kwargs is annotated as Mapping, and not dict or MutableMapping, a type-checker would raise an error if we do end up mutating.

The question is: would this be considered OK to do, or still a horrible idea?

It would be really nice not having to do the little subfunc_kwargs = {} if subfunc_kwargs is None else subfunc_kwargs dance and having neater signatures.

Note: **subfunc_kwargs is not an option since this potentially clashes with other keys and leads to issues if the kwargs of subfunc get changed.

2

There are 2 best solutions below

2
wjandrea On BEST ANSWER

You don't need to put yourself in this situation in the first place, where you're relying on a type checker, when you can put an actual immutable default argument:

from types import MappingProxyType

...
    subfunc_kwargs: Mapping[str, Any] = MappingProxyType({})

See What would a "frozen dict" be?

1
GabCaz On

"It would be really nice not having to do the little subfunc_kwargs = {} if subfunc_kwargs is None else subfunc_kwargs dance and having neater signatures."

Why so? Setting the default to None is a very robust choice in Python, and I would think robustness and clarity are preferable to elegance (in cases when they conflict). It is also very maintainable in the sense that it will be accessible to all developers, and well-understood by all static code analyzers.