Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a covariant mutable version of List?

I've boiled down the code which I actually want to anotate to this minimal version:

def print_it(numbers_or_nones):
    for i, number in enumerate(numbers_or_nones):
        if number is None:
            numbers_or_nones[i] = 0
            print("NOOOO")
        else:
            print(number)


numbers = [1, 2, 3, 4]
print_it(numbers)

I want to annotate the parameter numbers_or_nones of print_it. It needs to be...

  • ... a generic type where the elements are Optional[int]
  • ... iterable
  • ... support indexed assignment

What is the correct type for this case? Please note that it's not possible to change the type of numbers : List[int]. The only option I can see is to use typing.overload.

List

The simplest thing would be List[Optional[int]]. However, this gives:

error: Argument 1 to "print_it" has incompatible type "List[int]"; expected "List[Optional[int]]"
note: "List" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance
note: Consider using "Sequence" instead, which is covariant

Sequence

Unsupported target for indexed assignment ("Sequence[Optional[int]]")

MutableSequence

error: Argument 1 to "print_it" has incompatible type "List[int]"; expected "MutableSequence[Optional[int]]"
like image 336
Martin Thoma Avatar asked Jan 28 '26 04:01

Martin Thoma


1 Answers

Short answer: there are no covariant mutable collections, possible strategies to handle this situation are listed here https://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance


Why can't mutable collections be covariant? Because that would lead to serious issues like:

def make_first_None(numbers_or_nones: MutableSequence[Optional[int]]):
    numbers_or_nones[0] = None


numbers: List[int] = [1, 2, 3, 4]
make_first_None(numbers) # Error!! Numbers is not a List[int] anymore!!!

A longer explanation for why mutable collections must be invariant is here https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generics


For this case, the three strategies listed in the docs would look like:

  • Use an explicit type annotation:
numbers: List[Optional[int]] = [1, 2, 3, 4]
print_it(numbers)
  • Make a copy and pass that
print_it(list(numbers))
  • Use immutable collections
def print_it(numbers_or_nones: Sequence[Optional[int]]) -> List[int]:
    for number in numbers:
        if number is None:
            print("NOOOO")
        else:
            print(number)
    return [number or 0 for number in numbers]


numbers = [1, 2, 3, 4]
print_it(numbers)
like image 83
Recursing Avatar answered Jan 30 '26 18:01

Recursing



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!