Используйте подтип с mypy - PullRequest
       10

Используйте подтип с mypy

0 голосов
/ 17 февраля 2020

У меня есть функция, которая берет список объектов и печатает его.

bc_directives = t.Union[
    data.Open,
    data.Close,
    data.Commodity,
    data.Balance,
    data.Pad,
    data.Transaction,
    data.Note,
    data.Event,
    data.Query,
    data.Price,
    data.Document,
    data.Custom,
]


def print_entries(entries: t.List[bc_directives], file: t.IO) -> None:
    pass

, но если я делаю:

accounts: t.List[bc_directives] = []

for entry in data.sorted(entries):
  if isinstance(entry, data.Open):
    accounts.append(entry)
    continue
accounts = sorted(accounts, key=lambda acc: acc.account)

# the attribute account does not exist for the other class.

print_entries(accounts)

Здесь у меня проблема. mypy жалуется, что другой класс не имеет атрибута учетной записи. Конечно, он сконструирован так.

Элемент «Товар» из «Объединение [Открыть, Закрыть, Товар, Баланс, Пад, Транзакция, Примечание, Событие, Запрос, Цена, Документ, Пользовательский]» не имеет атрибута "account"

Если я изменю определение учетных записей на t.List[data.Open], mypy будет жаловаться, когда я использовал print_entries. (но это должно быть лучше). Так, как я могу использовать поднабор союза и заставить mypy не жаловаться?

Ответы [ 2 ]

1 голос
/ 17 февраля 2020

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

from typing import IO, List, Sequence, Union

class Open:
    def __init__(self, account: int) -> None:
        self.account = account

class Commodity: pass


Directives = Union[Open, Commodity]

def print_entries(entries: Sequence[Directives]) -> None:
    for entry in entries:
        print(entry)


accounts: List[Open] = [Open(1), Open(2), Open(3)]
print_entries(accounts)

Причина, по которой print_entries принимает список типов ваших директив, заключается в том, что он вводит потенциальная ошибка в вашем коде - если бы print_entries должен был сделать entries.append(Commodities()), ваш список учетных записей больше не содержал бы только открытые объекты, нарушая безопасность типов.

Sequence - версия списка только для чтения и поэтому полностью обходит эту проблему, предоставляя ей меньше ограничений. (То есть List [T] является подклассом Sequence [T]).


Точнее, мы говорим, что Sequence является ковариантным типом: если, если у нас есть некоторые дочерний тип C, который подклассов родительского типа P (если P:> C), всегда верно, что Sequence [P]:> Sequence [C].

В отличие от списков invariant : List [P] и List [C] не будут иметь присущих друг другу отношений, и ни один из них не будет подклассами другого.

Вот сводная таблица различных видов отношений, общая информация c типы могут иметь:

              | Foo[P] :> Foo[C] | Foo[C] :> Foo[P] | Used for
--------------------------------------------------------------------------------------
Covariant     | True             | False            | Read-only types
Contravariant | False            | True             | Write-only types
Invariant     | False            | False            | Both readable and writable types
Bivariant     | True             | True             | Nothing (not type safe)
1 голос
/ 17 февраля 2020

Вы можете ввести обобщенный c тип объединения для print_entries arg, а затем использовать более строгий тип для accounts. Пример:

from typing import TypeVar


T = TypeVar('T', data.Open, data.Close, data.Commodity, ...)


def print_entries(entries: List[T]) -> None:
    ...


accounts: List[data.Open] = []
print_entries(accounts)

closed: List[data.Close] = []
print_entries(closed)  # etc
...