pythondefault-parameters

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


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.


Solution

  • 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?