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

Один из случаев, когда вы видите это в Python, - это метод __exit__ для менеджера контекста (класса).

import typing as t
from types import TracebackType
from contextlib import AbstractContextManager

class Acme(AbstractContextManager):
  def __exit__(self, exc_type: t.Type[Exception], exc: Exception,
      tb: Tracebacktype) -> None:
    ...

Этот конкретный случай может не иметь значения, потому что менеджеры контекста обрабатываются внутри Python, но я все же хотел бы понять, как выразить два аргумента, где первый является (супер-) типом второго.

Концептуально, моя проблема в том, что я хочу выразить, что значение exc имеет тип exc_type или некоторый подтип. В приведенном выше выражении я думаю, что mypy был бы вполне доволен аргументами вроде LookupError, RuntimeError('foo'), хотя RuntimeError не является типом LookupError. Есть ли лучший способ выразить это, когда mypy поймает такую ошибку?

< Сильный > Обновление

Попробуем тестовый пример с помощью TypeVars:

import typing as t

C = t.TypeVar('C', bound=t.Collection)

def f(t: t.Type[C], c: C) -> None:
  pass

f(t.Dict, [])

Я ожидаю, что mypy будет жаловаться на этот код, потому что хотя пустой список является типом коллекции, это не словарь.

2
kojiro 28 Апр 2020 в 05:36

2 ответа

Лучший ответ

Такого рода использование Type[some_type_var], по-видимому, не приветствуется в разделе PEP, где предоставляется только подмножество этой поддержки (особенно для привязки TypeVar к типам классов). К моему удивлению, в некоторых случаях вы можете выжать немного из этого поведения (но это особый случай, и это мне кажется неопределенным)

T = TypeVar('T', str, int, float, bytes)

class Foo:

    def bar(self, the_type: Type[T], the_value: T):
        print(isinstance(the_value, the_type))

f = Foo()
f.bar(str, "baz")
f.bar(str, b"baz")  # wrong!
f.bar(int, 1)
f.bar(int, 3.15159)  # wrong! (but no complaint)
f.bar(float, 1.0)
f.bar(float, 1)  # wrong! (but no complaint)
f.bar(float, b'1.0')  # wrong!

Дающий

so.py:nn: error: Value of type variable "T" of "bar" of "Foo" cannot be "object"
so.py:nn: error: Value of type variable "T" of "bar" of "Foo" cannot be "object"
Found 2 errors in 1 file (checked 1 source file)

Но только на первый взгляд для примитивных типов python (это не будет работать с типами пользовательского пространства) (и опять же, только некоторые из примитивных типов python, как показано с помощью error-miss с float и int).


На Mypy есть несколько связанных с этим проблем:

  1. о поведении аннотации класса
1
modesitt 28 Апр 2020 в 03:19

Вот для чего TypeVar! Вы хотите что-то вроде:

from types import TracebackType
from typing import Type, TypeVar
from contextlib import AbstractContextManager

_E = TypeVar('_E', bound=Exception)

class Acme(AbstractContextManager):
    def __exit__(
        self,
        exc_type: Type[_E],
        exc: _E,
        tb: Tracebacktype
    ) -> None:
    ...
1
Samwise 28 Апр 2020 в 02:51