致谢
反射助手
mypy
并未提供您所需的功能,但手动实现并不太难。
import sys
from typing import get_args, get_origin, get_type_hints, Generic, Protocol
from typing import _collect_type_vars, _eval_type, _strip_annotations
def _generic_mro(result, tp):
origin = get_origin(tp)
if origin is None:
origin = tp
result[origin] = tp
if hasattr(origin, "__orig_bases__"):
parameters = _collect_type_vars(origin.__orig_bases__)
if origin is tp and parameters:
result[origin] = origin[parameters]
substitution = dict(zip(parameters, get_args(tp)))
for base in origin.__orig_bases__:
if get_origin(base) in result:
continue
base_parameters = getattr(base, "__parameters__", ())
if base_parameters:
base = base[tuple(substitution.get(p, p) for p in base_parameters)]
_generic_mro(result, base)
def generic_mro(tp):
origin = get_origin(tp)
if origin is None and not hasattr(tp, "__orig_bases__"):
if not isinstance(tp, type):
raise TypeError(f"{tp!r} is not a type or a generic alias")
return tp.__mro__
result = {Generic: Generic, Protocol: Protocol}
_generic_mro(result, tp)
cls = origin if origin is not None else tp
return tuple(result.get(sub_cls, sub_cls) for sub_cls in cls.__mro__)
def _class_annotations(cls, globalns, localns):
hints = {}
if globalns is None:
base_globals = sys.modules[cls.__module__].__dict__
else:
base_globals = globalns
for name, value in cls.__dict__.get("__annotations__", {}).items():
if value is None:
value = type(None)
if isinstance(value, str):
value = ForwardRef(value, is_argument=False)
hints[name] = _eval_type(value, base_globals, localns)
return hints
def get_type_hints2(
obj, globalns=None, localns=None, include_extras=False, substitute_type_vars=False
):
if substitute_type_vars and (isinstance(obj, type) or isinstance(get_origin(obj), type)):
hints = {}
for base in reversed(generic_mro(obj)):
origin = get_origin(base)
if hasattr(origin, "__orig_bases__"):
parameters = _collect_type_vars(origin.__orig_bases__)
substitution = dict(zip(parameters, get_args(base)))
annotations = _class_annotations(get_origin(base), globalns, localns)
for name, tp in annotations.items():
if isinstance(tp, TypeVar):
hints[name] = substitution.get(tp, tp)
elif tp_params := getattr(tp, "__parameters__", ()):
hints[name] = tp[
tuple(substitution.get(p, p) for p in tp_params)
]
else:
hints[name] = tp
else:
hints.update(_class_annotations(base, globalns, localns))
return (
hints
if include_extras
else {k: _strip_annotations(t) for k, t in hints.items()}
)
else:
return get_type_hints(obj, globalns, localns, include_extras)
def is_generic_class(klass):
return hasattr(klass, '__orig_bases__') and getattr(klass, '__parameters__', None)
使用方法
from dataclasses import dataclass
from typing import TypeVar, Generic, List
from .introspection import get_type_hints2, is_generic_class
T1 = TypeVar('T1')
T2 = TypeVar('T2')
@dataclass
class MyGenericClass(Generic[T1, T2]):
val: T1
results: List[T2]
@dataclass
class BaseClass:
my_str: str
@dataclass
class MyTestClass(BaseClass, MyGenericClass[str, int]):
...
print(get_type_hints2(MyTestClass, substitute_type_vars=True))
print(get_type_hints2(BaseClass, substitute_type_vars=True))
print(get_type_hints2(MyGenericClass, substitute_type_vars=True))
print(is_generic_class(MyGenericClass))
print(is_generic_class(MyTestClass))
print(is_generic_class(BaseClass))
MyTestClass
并不是泛型类:所有类型变量都已绑定。请参见此问题以获取可工作的实现和解释。 - SUTerliakov