Как я могу различить два динамических типа? - PullRequest
1 голос
/ 28 октября 2019

Я проверяю тип проекта, который использует панд . Pandas не содержит аннотации типов и в наборе нет заглушки.

Как и следовало ожидать, mypy вызывает ошибку для этого простого примера:

class A:
    def method(self) -> int:
        return 1


class B(A):
    def method(self) -> float:
        return 1.1
$ mypy mypy_example.py 
mypy_example.py:11: error: Return type "float" of "method" incompatible with return type "int" in supertype "A"

Рассмотрим следующий пример:

class C:
    def method(self) -> pd.Series:
        return pd.Series([1, 2, 3])


class D(C):
    def method(self) -> pd.DataFrame:
        return pd.DataFrame({"a": [1, 2, 3]})

Как и ожидалось, mypy говорит, что для панд не найден стаб-файл, поэтому не находит ошибку.

$ mypy mypy_example.py 
mypy_example.py:1: error: No library stub file for module 'pandas'
mypy_example.py:1: note: (Stub files are from https://github.com/python/typeshed)
mypy_example.py:11: error: Return type "float" of "method" incompatible with return type "int" in supertype "A"

Я мог установитьignore_missing_imports, но это означает, что я пропускаю ошибку, которую хочу уловить.

Я пробовал несколько вещей в стаб-файлах безуспешно:

from typing import Any, NewType

# dynamic typing, but doesn't discriminate between Series and DataFrame
Series = Any
DataFrame = Any

# discriminates but doesn't dynamically type
Series = NewType('Series', object)
DataFrame = NewType('DataFrame', object)

Можно ли написатькороткая заглушка или аннотация типа, которая позволит мне воспользоваться преимуществами динамической типизации, но я пойму, что pd.Series и pd.DataFrame - это разные типы?

1 Ответ

1 голос
/ 28 октября 2019

Вместо того, чтобы пытаться заставить mypy различать два динамических класса, я бы вместо этого выбрал способ сделать их не динамическими (точнее, только частично динамическими), определивони как полноценные классы внутри ваших заглушек.

Вы можете начать с очень предварительного и минимального набора заглушек, определив два ваших класса следующим образом:

from typing import Any

class Series:
    def __init__(self, *args: Any, **kwargs: Any) -> None: ...
    def __getattr__(self, name: str) -> Any: ...

class DataFrame(Series):
    def __init__(self, *args: Any, **kwargs: Any) -> None: ...
    def __getattr__(self, name: str) -> Any: ...

Функция __getattr__ позволяет mypy понять, что ваш класс неполон и не полностью аннотирован. Это означает, что выполнение таких действий, как DataFrame().query(...), продолжит проверку типов, даже если эта функция никогда явно не добавлялась в ваш класс.

Конечно, если вы do решите добавить вПосле нескольких сигнатур методов mypy начнет проверку типов этих вызовов вместо того, чтобы оставлять их динамически типизированными. Это означает, что вы также можете постепенно добавлять более точные сигнатуры методов, если хотите, и, возможно, в конечном итоге полностью избавиться от __getattr__.

Если вы решите пойти по этому пути, вы можете найти существующий NumPy заглушки , чтобы быть хорошим источником вдохновения. И если вам нужны действительно точные типы, обсуждение здесь , вероятно, уместно.

Рекомендации по типам при написании неполных заглушек содержат дополнительную информацию о написании частичных заглушек, если вылюбопытны.

...