Функция Typehinting для приема массивов numpy

Мне трудно понять, почему mypy выдает ошибку в следующем примере.

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:

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

В частности, поскольку typing.Sequence требует итерируемости, обратимости и индексации, я думаю, что массив numpy также должен быть Sequence. Связано ли это с тем, что массивы numpy изменяемы, а типы Sequence должны быть неизменяемыми? Я заметил, что когда я меняю Sequence на Iterable, проблема исправлена. Но мне нужно иметь возможность индексировать x в compute.

Итак, как лучше всего указать тип функции compute, чтобы она могла принимать объекты с итерируемостью и индексацией?

Потому что numpy.ndarray объекты не соответствуют collections.abc.Sequence ABC, который должен быть доступен только для чтения, и не поддерживает .index и .count, которые являются частью интерфейса.

juanpa.arrivillaga 06.05.2023 21:53

почему бы вам просто не создать свой собственный протокол? тот, который требует __iter__ и __getitem__?

juanpa.arrivillaga 06.05.2023 21:55

@juanpa.arrivillaga ваш первый комментарий вводит в заблуждение: list совместим с Sequence и не является неизменным. Реализация не обязательно должна быть неизменной — просто гарантируется наличие немодифицирующих методов. Ничто не мешает вам добавлять модифицирующие методы, они просто невидимы/непригодны для использования в качестве членов Sequence. Только index и count мешают здесь.

SUTerliakov 06.05.2023 21:59

@SUTerliakov да, вы абсолютно правы, если мы говорим о аннотациях типов

juanpa.arrivillaga 06.05.2023 22:07
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
4
85
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте использовать np.ndarray в качестве подсказки типа для x. Я проверил type(np.linspace(0, 1, 10)), и он вернул np.ndarray

Однако в других местах я хотел бы иметь возможность вызывать compute для переменных, которые не являются массивами numpy.

natty 06.05.2023 21:16
Ответ принят как подходящий

np.ndarray не является Sequence (несовместимым с соответствующим протоколом), потому что np.ndarray не реализует методы .count и .index, которые необходимы для collections.abc.Sequence, см. таблицу методов по ссылке.

Вот актуальный вопрос.

Чтобы сделать что-то похожее на последовательность, совместимое с np.ndarray, я предлагаю определить ваш протокол:

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]: ...

Это в основном то же самое, что и Sequence определение в typeshed, за исключением того, что я использую Protocol вместо Generic (чтобы избежать добавления реализации - заглушки не нужны, плюс это делает структурные подтипы более очевидными) и опускаю методы .index и .count .

Теперь np.ndarray должен быть совместим с WeakSequence, а также с list или tuple, и вы можете использовать WeakSequence вместо collections.abc.Sequence в своих аннотациях.

Ах, хорошо, я пропустил, что у него должны быть методы .count и .index. Я думаю, что index просто означает, что он поддерживает индексацию. Думаю, я тоже пропустил требование подсчета.

natty 09.05.2023 02:14

Другие вопросы по теме