Как написать typehint / использовать typevar для метода фильтра? - PullRequest
0 голосов
/ 29 января 2020

У меня есть вспомогательный метод в Python, который возвращает список методов и аннотированные данные для каждого метода. Так что это диктат списков. Аннотированные данные выражаются классом Attribute.

Определение выглядит следующим образом:

# A filter predicate can be either an attribute object or a tuple/list of attribute objects.
AttributeFilter = Union['Attribute', Iterable['Attribute'], None]

# A class offering a helper method
class Mixin:
  def GetMethods(self, filter: AttributeFilter=Attribute) -> Dict[Callable, List[Attribute]]:
    pass

Этот синтаксис и соответствующая проверка типов работают нормально.
Причины я хотел бы чтобы улучшить его.


Пользователи обычно получают пользовательские атрибуты из класса Attribute. Я хотел бы express, если пользователь передает производный класс, такой как UserAttribute в GetMethods, то есть возвращает список списков UserAttribute s.

# Some user-defined attribute and some public data in it
class UserAttribute(Attribute):
  someData: str

# Create a big class
class Big(mixin):

  # Annotate a method with meta information
  @UserAttribute("hello")
  def method(self):
    pass

# Create an instance
prog = Big()

# search for all methods that have 'UserAttribute' annotations
methods = prog.GetMethods(filter=UserAttribute)
for method, attributes in methods:
  for attribute in attributes:
    print(attribute.someData)

Этот код может быть выполняется без проблем, но средство проверки типов PyCharm не знает, что поле someData существует для attribute в последней строке (вызов печати).

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

methods:Dict[Callable, List[UserAttribute]] = prog.GetMethods(filter=UserAttribute)

Этот подход копирует много кода.

Возможное решение 2: Есть можно абстрагировать Dict[Callable, List[UserAttribute]] в какой-то новый generi c, чтобы я мог использовать:

# pseudo code
UserGeneric[G] := Dict[Callable, List[G]]

# shorter usage
methods:UserGeneric[UserAttribute] = prog.GetMethods(filter=UserAttribute)

Возможное решение 3: В лучшем случае я бы хотел бы использовать TypeVar вот так:

Attr = TypeVar("Attr", Attribute)

# A filter predicate can be either an attribute object or a tuple/list of attribute objects.
AttributeFilter = Union[Attr, Iterable[Attr], None]

# A class offering a helper method
class Mixin:
  def GetMethods(self, filter: AttributeFilter=Attribute) -> Dict[Callable, List[Attr]]:
    pass

К сожалению, TypeVar ожидает как минимум 2 ограничения, таких как T = TypeVar("T", str, byte).


В конце концов, это Более сложный вариант простого примера показан на страницах справочника по печатанию:

T = TypeVar("T")

def getElement(l: List[T]) -> T:
  pass

Последний вопрос:
Как ограничить TypeVar T для определенных объектов класса и всех его подклассов, без необходимости объединения, как в приведенном выше примере str и byte.

Этот вопрос относится к https://github.com/Paebbels/pyAttributes .

1 Ответ

1 голос
/ 30 января 2020

К сожалению, TypeVar ожидает как минимум 2 ограничения, таких как T = TypeVar("T", str, byte).

На самом деле, TypeVar может работать только с одним ограничением. Чтобы сделать это, вы должны сделать что-то вроде T = TypeVar("T", bound=str).

. Для получения более подробной информации, я бы порекомендовал прочитать документы mypy на TypeVars с верхними границами - официальные документы для печати, к сожалению, не очень отточенные и часто кратко охватывают важные понятия, такие как TypeVars с верхними границами.


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

from typing import TypeVar, Union, Iterable, Dict, Callable, List

class Attribute: pass

class UserAttribute(Attribute): pass


TAttr = TypeVar("TAttr", bound=Attribute)


AttributeFilter = Union[TAttr, Iterable[TAttr], None]

class Mixin:
  def GetMethods(self,
                 filter: AttributeFilter[TAttr] = Attribute,
                 ) -> Dict[Callable, List[TAttr]]:
    pass

m = Mixin()

# Revealed type is 'builtins.dict[def (*Any, **Any) -> Any, builtins.list[test.UserAttribute*]]'
reveal_type(m.GetMethods([UserAttribute(), UserAttribute()]))

Некоторые примечания:

  1. Я назвал свой TypeVar TAttr, а не Attr. Вы хотите прояснить для читателя, что такое «заполнители» в ваших подписях, поэтому полужирным соглашением является префикс ваших TypeVars либо с T, либо с _T. (И если вам не нужна верхняя граница и вместо этого вам нужен действительно открытый заполнитель, то условием является использование одной заглавной буквы, например T или S.)

  2. В GetMethods(...) нужно сделать AttributeFilter[TAttr]. Если вы просто наберете AttributeFilter, это на самом деле эквивалентно AttributeFilter[Any]. Это по той же причине, почему выполнение List означает то же самое, что и List[Any].

  3. Я снова использую TAttr для определения псевдонима типа и GetMethods в основном для удобства, но вы также можете создать новый TypeVar и использовать его для GetMethods, если вы действительно хотите: это будет означать то же самое.

  4. reveal_type(...) - это специальное псевдо -функция, которую понимают некоторые средства проверки типов (например, mypy и pyre): она заставляет средство проверки типов выводить, что, по их мнению, является типом выражения.

  5. Не все средства проверки типов (такие как mypy) может поддерживать установку аргумента по умолчанию для обобщенного типа c. Если ваша программа проверки типов жалуется, вы, вероятно, можете обойти ее, создав перегрузку с вариантами без аргументов и с одним аргументом.

...