PEP560 引入了 cls.__orig_bases__
,且 typing
模块提供了 get_origin
和 get_args
两个实用函数来提取类型。然而,当你有一个复杂的类层次结构并具有多级泛型继承时,你需要遍历该层次结构并将具体类型参数向下传递到层次结构中。
例如,如果你有以下类:
from typing import Generic, Literal, LiteralString
from datetime import date
T = TypeVar("T")
L = TypeVar("L", bound=LiteralString)
F = TypeVar("F", bound=str)
class Thing(Generic[T, L]):
pass
class ChildThing(Thing[T, L], Generic[T, L]):
pass
class StringThing(ChildThing[T, Literal["string"]], Generic[F, T]):
pass
class DateThing(StringThing[Literal["date"], date]):
pass
然后:
print(get_args(DateThing.__orig_bases__[0]))
将输出:
(typing.Literal['date'], <class 'datetime.date'>)
并且不会为基类Thing
的参数T
提供类型。为此,您需要更复杂的逻辑:
from typing import (
Any,
Dict,
Generic,
Literal,
LiteralString,
Optional,
Tuple,
TypeVar,
get_args,
get_origin,
)
from datetime import date
T = TypeVar("T")
L = TypeVar("L", bound=LiteralString)
F = TypeVar("F", bound=str)
def get_generic_map(
base_cls: type,
instance_cls: type,
) -> Dict[Any, Any]:
"""Get a map from the generic type paramters to the non-generic implemented types.
Args:
base_cls: The generic base class.
instance_cls: The non-generic class that inherits from ``base_cls``.
Returns:
A dictionary mapping the generic type parameters of the base class to the
types of the non-generic sub-class.
"""
assert base_cls != instance_cls
assert issubclass(instance_cls, base_cls)
cls: Optional[type] = instance_cls
generic_params: Tuple[Any, ...]
generic_values: Tuple[Any, ...] = tuple()
generic_map: Dict[Any, Any] = {}
while cls is not None and issubclass(cls, base_cls):
if hasattr(cls, "__orig_bases__"):
bases = cls.__orig_bases__
generic_params = next(
(
get_args(generic)
for generic in bases
if get_origin(generic) is Generic
),
tuple(),
)
generic_map = (
{param: value for param, value in zip(generic_params, generic_values)}
if len(generic_params) > 0
else {}
)
generic_values = next(
(
tuple(
generic_map[arg] if arg in generic_map else arg
for arg in get_args(base)
)
for base in bases
if (
isinstance(get_origin(base), type)
and issubclass(get_origin(base), base_cls)
)
),
tuple(),
)
else:
generic_map = {}
assert isinstance(cls, type)
cls = next(
(base for base in cls.__bases__ if issubclass(base, base_cls)),
None,
)
return generic_map
class Thing(Generic[T, L]):
type: T
literal: L
def __init__(self) -> None:
super().__init__()
type_map = get_generic_map(Thing, type(self))
self.type = type_map[T]
self.literal = type_map[L]
def hello(self) -> str:
return f"I am type={self.type}, literal={self.literal}"
class ChildThing(Thing[T, L], Generic[T, L]):
pass
class StringThing(ChildThing[T, Literal["string"]], Generic[F, T]):
def __init__(self) -> None:
super().__init__()
type_map = get_generic_map(StringThing, type(self))
self.f = type_map[F]
def hello(self) -> str:
return f"{super().hello()}, f={self.f}"
class DateThing(StringThing[Literal["date"], date]):
pass
thing = DateThing()
print(thing.hello())
输出结果为:
I am type=<class 'datetime.date'>, literal=typing.Literal['string'], f=typing.Literal['date']
type
方法吗?比如my_type = type(T)
? - DavidG