用户自定义泛型类型别名(透明注释)

问题描述 投票:0回答:2

我想定义一个自定义类型,其行为与它所包装的类型完全相同,就像类型别名,但作为泛型。我在官方文档中没有找到任何与我的问题相匹配的内容。这是我到目前为止尝试过的:

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]]
。我需要一个不影响类型系统的泛型类型别名,或者一个允许从基础类型进行隐式转换的新类型。

python python-3.x type-hinting
2个回答
1
投票

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

0
投票

@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
类型,但我认为时间投入不值得。

© www.soinside.com 2019 - 2024. All rights reserved.