mypy: предъявляйте более строгие требования к типу без затрат времени выполнения

Я получаю сообщения от удаленной стороны, которые декодируются в классы, которые выглядят следующим образом:

class SomeMessage(MessageType):
    foo: Optional[int]
    bar: Optional[str]
    quux: Optional[AnotherMessageType]

Большинство полей всегда являются необязательными. Телеграфное сообщение может содержать их, а может и не содержать.

Однако мой код выполняет проверку заранее, а затем в основном предполагает наличие соответствующих полей.

E.g.:

def process_all_foos(messages: List[SomeMessage]):
    messages_with_foo = [m for m in messages if m.foo is not None]
    foo_sum = sum(m.foo for m in messages_with_foo)

Этот код не проходит проверку типов, потому что mypy не выполняет предыдущую проверку.

Есть ли возможность каким-то образом выполнить проверку типов приведенного выше кода без затрат времени выполнения?

Моя идея заключалась в создании протокола с более строгим требованием:

class SomeMessageWithFoo(Protocol):
    foo: int

def convert_foo(m: SomeMessage) -> SomeMessageWithFoo:
    assert m.foo is not None
    return m

# or, ideally:
def process_all_foos(messages: List[SomeMessage]):
    messages_with_foo: List[SomeMessageWithFoo] = [m for m in messages if m.foo is not None]
    foo_sum = sum(m.foo for m in messages_with_foo)

Однако mypy отвергает это:

test.py:16: error: Incompatible return value type (got "SomeMessage", expected "SomeMessageWithFoo")  [return-value]
test.py:16: note: Following member(s) of "SomeMessage" have conflicts:
test.py:16: note:     foo: expected "int", got "Optional[int]"
test.py:20: error: List comprehension has incompatible type List[SomeMessage]; expected List[SomeMessageWithFoo]  [misc]
test.py:20: note: Following member(s) of "SomeMessage" have conflicts:
test.py:20: note:     foo: expected "int", got "Optional[int]"

Я рассматривал возможность использования cast(SomeMessageWithFoo, m) или, что более вероятно, # type: ignore, потому что это не требует затрат времени выполнения. (Я работаю во встроенной среде, поэтому меня волнует вызов функции, который делает cast.) Но это также позволяет мне не проверять поле.

То есть, следующие неправильные проверки типов:

def convert_foo(m: SomeMessage) -> SomeMessageWithFoo:
    # someone commented this out:
    #assert m.foo is not None
    return cast(SomeMessageWithFoo, m)

Есть ли способ сделать это, который выполняет проверку типа кода, но при этом фактически проверяет требования?


person matejcik    schedule 08.06.2020    source источник


Ответы (1)


Я не понимаю. Разве это не очевидное решение, поскольку оно а) ускоряет ваш код, поскольку вы делаете только одну итерацию вместо двух, и б) исправляет проверки типов:

def process_all_foos(messages: List[SomeMessage]):
    foo_sum = sum(m.foo for m in messages if m.foo is not None)
    # use foo_sum
person ruohola    schedule 11.06.2020
comment
дело в том, что я не хочу повторять if m.foo is not None во всех местах; будет несколько циклов for (где я не могу написать это встроенно), некоторые понимания, возможно, такие вызовы, как self.calculate(messages_with_foo[-1]). Во всех этих случаях я хочу, чтобы тип messages_with_foo был List[SomeMessage where I know foo is not None] - person matejcik; 15.06.2020