我想定义一个自定义类型,其行为与它所包装的类型完全相同,就像类型别名,但作为泛型。我在官方文档中没有找到任何与我的问题相匹配的内容。这是我到目前为止尝试过的:
T = TypeVar('T')
# Doesn't work.
# Special = TypeAlias[Generic[T]]
class Special(Generic[T]): # This is a new type, but in reality I want a generic alias.
pass
var1: Special[dict[str, str]] = {"a": "b"}
# error: Expression of type `dict[str, str]` cannot be assigned to `Special[dict[str, str]]`
我主要需要一种方法来仅在运行时区分一些特殊变量。我可以通过注释像
special_var: 'Special'
这样的变量来做到这一点,但这显然隐藏了底层的真实类型并破坏了类型检查。
相反,我需要的是一种对类型系统完全透明的类型,但仍然允许我区分,例如
dict[str, str]
来自Special[dict[str, str]]
。我需要一个不影响类型系统的泛型类型别名,或者一个允许从基础类型进行隐式转换的新类型。
typing.Annotated
由 PEP 593 在 Python 3.9 中引入,正是您正在寻找的。
在 PEP 593(灵活函数和变量注释)中引入的一种类型,用于使用上下文特定的元数据(可能是多个元数据,因为
是可变参数)来装饰现有类型。具体来说,类型Annotated
可以通过类型提示T
用元数据x
进行注释。该元数据可用于静态分析或运行时。如果库(或工具)遇到类型提示Annotated[T, x]
并且对于元数据没有特殊逻辑Annotated[T, x]
,它应该忽略它并简单地将类型视为x
。T
所以特殊的自定义类型可以像下面这样表达和检查。
In [1]: import sys
...: from typing import Annotated, Dict, get_type_hints, get_args, get_origin
In [2]: SpecialTypeMarker = object()
...:
...: var1: Annotated[Dict[str, str], SpecialTypeMarker] = {'a': 'b'}
...: var2: Dict[str, str] = {'b': 'c'}
In [3]: hints = get_type_hints(sys.modules[__name__], include_extras=True)
...:
...: print(SpecialTypeMarker in getattr(hints['var1'], '__metadata__', ()))
...: print(SpecialTypeMarker in getattr(hints['var2'], '__metadata__', ()))
True
False
In [4]: def is_special(hint):
...: '''Alternatively using get_origin and get_args'''
...:
...: if get_origin(hint) is not Annotated:
...: return False
...:
...: return SpecialTypeMarker in get_args(hint)[1:]
...:
...: print(is_special(hints['var1']))
...: print(is_special(hints['var2']))
True
False
@saaj 的答案是正确的,但没有显示如何创建自己的透明注释或泛型类型别名。
这是一个简单的例子:
from typing import Annotated, TypeVar
T = TypeVar('T')
class YourCustomMarker:
pass
YourCustomAlias = Annotated[T, YourCustomMarker]
test_var: YourCustomAlias[str] = 'test' # Typechecker says it's valid!
理论上,您不需要
YourCustomMarker
,但请查看当您访问此示例函数的 __annotation__
属性时会发生什么:
def test(input: YourCustomAlias[str]):
pass
你得到
{'input': typing.Annotated[str, <class '__main__.YourCustomMarker'>]}
。
您的别名将转换回 Annotated
,区分您的注释与其他注释的唯一方法是为其提供某种元数据。
理论上,我们可以实现自己的在运行时保留的
Annotated
类型,但我认为时间投入不值得。