首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用泛型类型子类的类的最佳方法是什么?

使用泛型类型子类的类的最佳方法是什么?
EN

Stack Overflow用户
提问于 2022-05-13 21:32:47
回答 1查看 246关注 0票数 4

假设我们得到了一个泛型类定义,如:

代码语言:javascript
运行
复制
from dataclasses import dataclass
from typing import TypeVar, Generic, List


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]):
    ...

确定MyTestClass是一个泛型类的最佳方法是什么--也就是说,与常规数据类型相反?

此外,在这种情况下,typing模块提供了将泛型类型(TypeVar)解析为具体类型关系的简单方法吗?

为了前夫。考虑到上面的results: List[T2],我想了解MyTestClass上下文中的T2将解析为int类型。

确定类是否是泛型的

目前,如果运行set(vars(MyTestClass)) - set(vars(BaseClass)),将得到以下结果:

代码语言:javascript
运行
复制
{'__parameters__', '__orig_bases__'}

但是,我想知道typing是否提供了一种简单的方法来确定类是泛型类还是泛型类的子类,例如typing.Dict

所以对is_cls_generic()的影响是我感兴趣的东西。

解析TypeVar类型

目前,当我调用typing.get_type_hints(MyTestClass)时,会得到以下结果:

代码语言:javascript
运行
复制
{'val': ~T1, 'results': typing.List[~T2], 'my_str': <class 'str'>}

我想知道typing模块是否提供了一种简单的方法来解析这些TypeVar变量,以便所期望的结果是:

代码语言:javascript
运行
复制
{'val': str, 'results': typing.List[int], 'my_str': str}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-05-18 10:26:58

学分

自省帮手

mypy不提供您需要的功能,但是要手动实现它并不太困难。

代码语言:javascript
运行
复制
# introspection.py

# mypy issue 776
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__
    # sentinel value to avoid to subscript Generic and Protocol
    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


# For brevety of the example, the implementation just add the substitute_type_vars
# implementation and default to get_type_hints. Of course, it would have to be directly
# integrated into get_type_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)

# Generic classes that accept at least one parameter type.
# It works also for `Protocol`s that have at least one argument.
def is_generic_class(klass):
    return hasattr(klass, '__orig_bases__') and getattr(klass, '__parameters__', None)

用法

代码语言:javascript
运行
复制
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))
# {'val': <class 'str'>, 'results': typing.List[int], 'my_str': <class 'str'>}
print(get_type_hints2(BaseClass, substitute_type_vars=True))
# {'my_str': <class 'str'>}
print(get_type_hints2(MyGenericClass, substitute_type_vars=True))
# {'val': ~T1, 'results': typing.List[~T2]}
print(is_generic_class(MyGenericClass))
# True
print(is_generic_class(MyTestClass))
# False
print(is_generic_class(BaseClass))
# False
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72235622

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档