1

I'm having trouble understanding why mypy throws an error with the following example.

import numpy as np
from typing import Sequence


def compute(x: Sequence[float]) -> bool:
    # some computation that does not modify x
    ...


compute(np.linspace(0, 1, 10))

Mypy Error:

Argument 1 to "compute" has incompatible type "ndarray[Any, dtype[floating[Any]]]"; expected "Sequence[float]"  [arg-type]

In particular, since typing.Sequence requires iterability, reversability, and indexing, I'd think a numpy array should also be a Sequence. Is it related to the fact that numpy arrays are mutable, and Sequence types are intended to be immutable? I notice when I change Sequence to Iterable, the problem is fixed. But I need to be able to index x in compute.

So, what is the best way to typehint the compute function so that it can accept objects with iterability and indexing?

natty
  • 69
  • 6
  • Because `numpy.ndarray` objects do not conform to the `collections.abc.Sequence` ABC, which must be read-only, nor does it support `.index` and `.count` which are part of the interface. – juanpa.arrivillaga May 06 '23 at 19:53
  • why don't you just create your own protocol? one that requires `__iter__` and `__getitem__`? – juanpa.arrivillaga May 06 '23 at 19:55
  • 1
    @juanpa.arrivillaga your first comment is misleading: `list` is compatible with `Sequence`, and it is not immutable. The implementation does not have to be immutable - it's just guaranteed only to have non-modifying methods. Nothing prevents you from adding modifying methods, they're just invisible/unusable as `Sequence` members. Only `index` and `count` make a trouble here. – STerliakov May 06 '23 at 19:59
  • @SUTerliakov yes, you are absolutely right if we are talking about type annotations – juanpa.arrivillaga May 06 '23 at 20:07

2 Answers2

3

np.ndarray is not a Sequence (incompatible with corresponding protocol), because np.ndarray does not implement .count and .index methods, which are required for collections.abc.Sequence, see the table of methods in link.

Here's a relevant question.

To make something sequence-like compatible with np.ndarray, I'd suggest to define your protocol:

from __future__ import annotations

from collections.abc import Collection, Reversible, Iterator
from typing import Protocol, TypeVar, overload

_T_co = TypeVar('_T_co', covariant=True)

class WeakSequence(Collection[_T_co], Reversible[_T_co], Protocol[_T_co]):
    @overload
    def __getitem__(self, index: int) -> _T_co: ...
    @overload
    def __getitem__(self, index: slice) -> WeakSequence[_T_co]: ...
    def __contains__(self, value: object) -> bool: ...
    def __iter__(self) -> Iterator[_T_co]: ...
    def __reversed__(self) -> Iterator[_T_co]: ...

This is basically the same as Sequence definition in typeshed except that I'm using Protocol instead of Generic (to avoid adding implementation - stubs don't have to, plus this makes structural subtyping more obvious) and omit .index and .count methods.

Now np.ndarray should be compatible with WeakSequence as well as a list or tuple, and you can use WeakSequence instead of collections.abc.Sequence in your annotations.

STerliakov
  • 4,983
  • 3
  • 15
  • 37
  • Ahhh okay I missed that it had to have `.count` and `.index` _methods_. I think index just meant that it supported indexing. I guess I missed the count requirement too. – natty May 09 '23 at 00:14
0

Try using np.ndarray as the type hint for x. I checked type(np.linspace(0, 1, 10)) and it returned np.ndarray

olubode
  • 26
  • 5
  • In other places I'd like to be able to call `compute` on variables that are not numpy arrays, however. – natty May 06 '23 at 19:16