如何编写类型提示/将类型变量用于过滤方法?
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 是 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 的类型检查器不知道最后一行(打印调用)中的 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 示例中的联合。
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()]))
一些注意事项:
我将我的 TypeVar 命名为 TAttr
,而不是 Attr
。你想让 reader 明白你签名中的 "placeholders" 是什么,所以半通用的约定是在你的 TypeVars 前加上 T
或 _T
前缀。 (如果你不需要上限,而是想要一个真正的开放式占位符,惯例是使用单个大写字母,如 T
或 S
。)
在GetMethods(...)
中,你需要做AttributeFilter[TAttr]
。如果你只做 AttributeFilter
,那实际上等同于做 AttributeFilter[Any]
。这与 List
与 List[Any]
.
的含义相同。
我重用 TAttr
来定义类型别名和 GetMethods
主要是为了方便,但您也可以创建一个新的 TypeVar 并将其用于 GetMethods
如果你真的想要:那将意味着完全相同的事情。
reveal_type(...)
是一些类型检查器(例如 mypy 和 pyre)理解的特殊伪函数:它使类型检查器打印出它认为表达式的类型。
并非所有类型检查器(例如 mypy)都支持为泛型类型设置默认参数。如果你的类型检查器抱怨,你可以通过创建一个无参数和单参数变体的重载来解决它。
我在 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 是 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 的类型检查器不知道最后一行(打印调用)中的 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 示例中的联合。
Unfortunately,
TypeVar
expects at least 2 constraints likeT = 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()]))
一些注意事项:
我将我的 TypeVar 命名为
TAttr
,而不是Attr
。你想让 reader 明白你签名中的 "placeholders" 是什么,所以半通用的约定是在你的 TypeVars 前加上T
或_T
前缀。 (如果你不需要上限,而是想要一个真正的开放式占位符,惯例是使用单个大写字母,如T
或S
。)在
GetMethods(...)
中,你需要做AttributeFilter[TAttr]
。如果你只做AttributeFilter
,那实际上等同于做AttributeFilter[Any]
。这与List
与List[Any]
. 的含义相同。
我重用
TAttr
来定义类型别名和GetMethods
主要是为了方便,但您也可以创建一个新的 TypeVar 并将其用于GetMethods
如果你真的想要:那将意味着完全相同的事情。reveal_type(...)
是一些类型检查器(例如 mypy 和 pyre)理解的特殊伪函数:它使类型检查器打印出它认为表达式的类型。并非所有类型检查器(例如 mypy)都支持为泛型类型设置默认参数。如果你的类型检查器抱怨,你可以通过创建一个无参数和单参数变体的重载来解决它。