Mar*_*hac 7 python type-hinting mypy python-typing
鉴于这种:
from typing import Generic, TypeVar
T = TypeVar('T')
class Parent(Generic[T]):
pass
Run Code Online (Sandbox Code Playgroud)
我可以int从Parent[int]使用中得到typing.get_args(Parent[int])[0]。
问题变得有点复杂,如下所示:
class Child1(Parent[int]):
pass
class Child2(Child1):
pass
Run Code Online (Sandbox Code Playgroud)
为了支持任意长的继承层次结构,我提出了以下解决方案:
import typing
from dataclasses import dataclass
@dataclass(frozen=True)
class Found:
value: Any
def get_parent_type_parameter(child: type) -> Optional[Found]:
for base in child.mro():
# If no base classes of `base` are generic, then `__orig_bases__` is nonexistent causing an `AttributeError`.
# Instead, we want to skip iteration.
for generic_base in getattr(base, "__orig_bases__", ()):
if typing.get_origin(generic_base) is Parent:
[type_argument] = typing.get_args(generic_base)
# Return `Found(type_argument)` instead of `type_argument` to differentiate between `Parent[None]`
# as a base class and `Parent` not appearing as a base class.
return Found(type_argument)
return None
Run Code Online (Sandbox Code Playgroud)
这样get_parent_type_parameter(Child2)返回int. 我只对一个特定基类 ( ) 的类型参数感兴趣Parent,因此我将该类硬编码到其中get_parent_type_parameter并忽略任何其他基类。
但我的上述解决方案因这样的链而崩溃:
class Child3(Parent[T], Generic[T]):
pass
Run Code Online (Sandbox Code Playgroud)
其中get_parent_type_parameter(Child3[int])返回T而不是int.
虽然任何解决问题的答案Child3都已经很好了,但能够处理这样的情况Child4会更好:
from typing import Sequence
class Child4(Parent[Sequence[T]], Generic[T]):
pass
Run Code Online (Sandbox Code Playgroud)
所以get_parent_type_parameter(Child4[int])返回Sequence[int]。
是否有一种更可靠的方法可以在运行时访问类的类型参数(X给定注释Awhere issubclass(typing.get_origin(A), X)is )True?
为什么我需要这个:
最近的 Python HTTP 框架根据函数的带注释的返回类型生成端点文档(和响应架构)。例如:
app = ...
@dataclass
class Data:
hello: str
@app.get("/")
def hello() -> Data:
return Data(hello="world")
Run Code Online (Sandbox Code Playgroud)
我正在尝试扩展它以解释状态代码和其他非正文组件:
@dataclass
class Error:
detail: str
class ClientResponse(Generic[T]):
status_code: ClassVar[int]
body: T
class OkResponse(ClientResponse[Data]):
status_code: ClassVar[int] = 200
class BadResponse(ClientResponse[Error]):
status_code: ClassVar[int] = 400
@app.get("/")
def hello() -> Union[OkResponse, BadResponse]:
if random.randint(1, 2) == 1:
return OkResponse(Data(hello="world"))
return BadResponse(Error(detail="a_custom_error_label"))
Run Code Online (Sandbox Code Playgroud)
为了生成OpenAPI文档,我的框架将在检查传递给 的函数的带注释的返回类型后对每个框架进行评估get_parent_type_parameter(E)(硬编码为中ClientResponse的父级get_parent_type_parameter)。所以首先会导致. 那么就会这样,导致。然后,我的框架会迭代每种类型,并在客户端的文档中生成响应模式。EUnionapp.getEOkResponseDataErrorResponseError__annotations__body
以下方法基于__class_getitem__和__init_subclass__。它可能适合您的用例,但它有一些严重的限制(见下文),因此请根据您自己的判断使用。
from __future__ import annotations
from typing import Generic, Sequence, TypeVar
T = TypeVar('T')
NO_ARG = object()
class Parent(Generic[T]):
arg = NO_ARG # using `arg` to store the current type argument
def __class_getitem__(cls, key):
if cls.arg is NO_ARG or cls.arg is T:
cls.arg = key
else:
try:
cls.arg = cls.arg[key]
except TypeError:
cls.arg = key
return super().__class_getitem__(key)
def __init_subclass__(cls):
if Parent.arg is not NO_ARG:
cls.arg, Parent.arg = Parent.arg, NO_ARG
class Child1(Parent[int]):
pass
class Child2(Child1):
pass
class Child3(Parent[T], Generic[T]):
pass
class Child4(Parent[Sequence[T]], Generic[T]):
pass
def get_parent_type_parameter(cls):
return cls.arg
classes = [
Parent[str],
Child1,
Child2,
Child3[int],
Child4[float],
]
for cls in classes:
print(cls, get_parent_type_parameter(cls))
Run Code Online (Sandbox Code Playgroud)
其输出如下:
__main__.Parent[str] <class 'str'>
<class '__main__.Child1'> <class 'int'>
<class '__main__.Child2'> <class 'int'>
__main__.Child3[int] <class 'int'>
__main__.Child4[float] typing.Sequence[float]
Run Code Online (Sandbox Code Playgroud)
此方法要求 every Parent[...](ie __class_getitem__) 后跟一个__init_subclass__,否则前一个信息可能会被第二个覆盖Parent[...]。因此,它不适用于类型别名。考虑以下:
__main__.Parent[str] <class 'str'>
<class '__main__.Child1'> <class 'int'>
<class '__main__.Child2'> <class 'int'>
__main__.Child3[int] <class 'int'>
__main__.Child4[float] typing.Sequence[float]
Run Code Online (Sandbox Code Playgroud)
其输出:
__main__.Parent[str] <class 'float'>
__main__.Parent[int] <class 'float'>
__main__.Parent[float] <class 'float'>
Run Code Online (Sandbox Code Playgroud)