1

I recently received this good answer employed in the following:

from typing import Union, cast


class Default:
    """Placeholder for default arguments."""


# ham[] is mutable.  `None` has meaning (or is not preferred).
def spam(ham: Union[list[str], None, type[Default]] = Default):
    if ham is Default:
        ham = ['prosciutto', 'jamon']
    #ham = cast(Union[list[str], None], ham)
    #assert isinstance(ham, (list, type(None)))
    if ham is None:
        print('Eggs?')
    else:
        print(str(len(ham)) + ' ham(s).')

mypy error:

Failed (exit code: 1) (2655 ms)

main.py:17: error: Argument 1 to "len" has incompatible type "Union[List[str], Type[Default]]"; expected "Sized"
Found 1 error in 1 file (checked 1 source file)

To avoid similar mypy errors, I've always used one of the two idioms commented out.

Are there other solutions?

  • 1
    This is, I think, one of the main reasons why there is a PEP being proposed to provide a better solution to the problem of sentinel values. MyPy can't understand custom type-narrowing, which is what you're doing with your `if ham is Default` check; it can only understand specific kinds of type narrowing such as using `isinstance`, `assert`, or raising exceptions. I'd be tempted to just suppress the error with `# type: ignore`; your code is perfectly type-safe. – Alex Waygood Sep 25 '21 at 16:40
  • 1
    @AlexWaygood I'll keep that in mind. Thanks for the affirmation. – zatg98n4qwsb8heo Sep 25 '21 at 18:21

1 Answers1

5

Why not use an instance of the Default class instead of the class itself? MyPy understands isinstance and can infer the type of objects used in it, in contrast to type-narrowing that uses is.

As for using an instance of a class as a default value for a parameter: according to the function definition section of the Python documentation, there is no significant overhead and it can be neglected:

Default parameter values are evaluated from left to right when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call.


For example:

from typing import Union, cast


class Default:
    """Placeholder for default arguments."""


# ham[] is mutable.  `None` has meaning (or is not preferred).
def spam(ham: Union[list[str], None, Default] = Default()):
    if isinstance(ham, Default):
        ham = ['prosciutto', 'jamon']
    if ham is None:
        print('Eggs?')
    else:
        print(str(len(ham)) + ' ham(s).')
Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
alex_noname
  • 26,459
  • 5
  • 69
  • 86