如何编写类型提示/将类型变量用于过滤方法?

How to write a typehint / use a typevar for a filter method?

我在 Python 中有一个辅助方法,return 是一个方法列表和每个方法的注释数据。所以这是一个列表的字典。注释数据由 Attribute class.

表示

定义如下:

# 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

此语法和相应的类型检查工作正常。
因为我想改进它。


用户通常从 class Attribute 派生用户定义的属性。我想表达的是,如果用户将 UserAttribute 之类的派生 class 传递给 GetMethods,即 returns 是 UserAttributes 列表的字典。

# 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 的类型检查器不知道最后一行(打印调用)中的 attribute 字段 someData 存在。

可能的方案一: 我可以为每个从 GetMethods 获取 return 值的变量使用类型提示,如下所示:

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

这种方法重复了很多代码。

可能方案二: 是否可以将 Dict[Callable, List[UserAttribute]] 抽象成某种新的 generic 以便我可以使用:

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

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

可能方案三: 充其量我想使用这样的 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 约束到 class 的某些对象及其所有子 classes,而不需要像上面的 str 与 byte 示例中的联合。

此问题与https://github.com/Paebbels/pyAttributes有关。

Unfortunately, TypeVar expects at least 2 constraints like T = TypeVar("T", str, byte).

实际上,TypeVar 可以只使用一个约束。为此,您可以执行类似 T = TypeVar("T", bound=str).

的操作

有关更多详细信息,我建议阅读 mypy docs on TypeVars with upper bounds——遗憾的是,官方打字文档不是很完善,并且经常非常简要地涵盖重要的概念,例如 TypeVars with upper bounds。


所以,这意味着您可以通过这样做来解决您的问题:

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。你想让 reader 明白你签名中的 "placeholders" 是什么,所以半通用的约定是在你的 TypeVars 前加上 T_T 前缀。 (如果你不需要上限,而是想要一个真正的开放式占位符,惯例是使用单个大写字母,如 TS。)

  2. GetMethods(...)中,你需要做AttributeFilter[TAttr]。如果你只做 AttributeFilter,那实际上等同于做 AttributeFilter[Any]。这与 ListList[Any].

  3. 的含义相同。
  4. 我重用 TAttr 来定义类型别名和 GetMethods 主要是为了方便,但您也可以创建一个新的 TypeVar 并将其用于 GetMethods 如果你真的想要:那将意味着完全相同的事情。

  5. reveal_type(...) 是一些类型检查器(例如 mypy 和 pyre)理解的特殊伪函数:它使类型检查器打印出它认为表达式的类型。

  6. 并非所有类型检查器(例如 mypy)都支持为泛型类型设置默认参数。如果你的类型检查器抱怨,你可以通过创建一个无参数和单参数变体的重载来解决它。