You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Unique等泛型代理类型对静态类型检查器透明?

实现透明的ORM约束泛型类型

要让Unique[T]、Index[T]和Primary[T]这类泛型类型在IDE和静态检查器中被视为T,同时在运行时保留约束标记,最简洁且符合PEP标准的方案是利用typing.Annotated(PEP 593)来封装这些约束。下面是具体实现思路和代码:

方案一:基于Annotated的透明代理实现

核心思路是通过__class_getitem__让约束类返回Annotated[T, Marker]——类型检查器会自动忽略Annotated的元数据部分,将其视为T,而我们可以在运行时提取元数据来识别约束。

完整代码示例

from typing import TypeVar, Generic, Annotated, get_type_hints
import inspect

T = TypeVar('T')

# 定义运行时识别约束的标记类
class UniqueMarker:
    pass

class IndexMarker:
    pass

class PrimaryMarker:
    pass

class Unique(Generic[T]):
    @classmethod
    def __class_getitem__(cls, item):
        # 返回Annotated类型,静态检查器会将其视为item本身
        return Annotated[item, UniqueMarker]
    
    def __new__(cls, *args, **kwargs):
        raise TypeError(f'cannot instantiate {cls.__name__}')

class Index(Generic[T]):
    @classmethod
    def __class_getitem__(cls, item):
        return Annotated[item, IndexMarker]
    
    def __new__(cls, *args, **kwargs):
        raise TypeError(f'cannot instantiate {cls.__name__}')

class Primary(Generic[T]):
    @classmethod
    def __class_getitem__(cls, item):
        return Annotated[item, PrimaryMarker]
    
    def __new__(cls, *args, **kwargs):
        raise TypeError(f'cannot instantiate {cls.__name__}')

# 示例ORM模型
class User:
    id: Primary[int]
    username: Unique[str]
    email: Index[str]
    age: int

# 运行时提取字段约束的工具函数
def parse_model_constraints(model_cls):
    # include_extras=True 保留Annotated的完整信息
    type_hints = get_type_hints(model_cls, include_extras=True)
    field_constraints = {}
    
    for field_name, hint in type_hints.items():
        base_type = hint
        has_unique = False
        has_index = False
        has_primary = False
        
        # 检查是否是Annotated类型
        if isinstance(hint, Annotated):
            base_type = hint.__args__[0]
            markers = hint.__args__[1:]
            
            has_unique = any(isinstance(m, UniqueMarker) for m in markers)
            has_index = any(isinstance(m, IndexMarker) for m in markers)
            has_primary = any(isinstance(m, PrimaryMarker) for m in markers)
        
        field_constraints[field_name] = {
            "type": base_type,
            "primary": has_primary,
            "unique": has_unique,
            "index": has_index
        }
    
    return field_constraints

# 测试输出
print(parse_model_constraints(User))

方案优势

  1. 类型检查友好:所有主流静态检查器(mypy、Pyright、Pytype)和IDE(PyCharm、VS Code)都会将Unique[str]识别为str,代码补全、类型提示完全正常。
  2. 运行时可识别:通过get_type_hints并设置include_extras=True,可以轻松提取字段的约束信息,用于生成数据库表结构。
  3. 写法简洁:用户只需写Unique[str],无需额外的Annotated语法,符合直觉。

兼容性说明

如果需要支持Python 3.8及以下版本,可以从typing_extensions库导入Annotated(该库是官方维护的,兼容所有Python版本)。

方案二:不依赖Annotated的兼容声明(适合老版本)

如果无法使用Annotated,可以通过Protocol的__subclasshook__让类型检查器认为约束类与T兼容,但这种方案兼容性较差,且实现复杂:

from typing import TypeVar, Generic, Protocol
from typing_extensions import Protocol  # Python 3.8及以下需要

T = TypeVar('T')

class Unique(Generic[T]):
    def __new__(cls, *args, **kwargs):
        raise TypeError(f'cannot instantiate {cls.__name__}')

# 定义协议,让类型检查器认为任何T的实例都匹配Unique[T]
class _UniqueCompatible(Protocol[T]):
    @classmethod
    def __subclasshook__(cls, subclass):
        return issubclass(subclass, T)

# 告诉类型检查器Unique[T]与_UniqueCompatible[T]等价(需配合mypy等检查器的配置)
Unique = Unique[_UniqueCompatible[T]]  # 仅用于类型推断,运行时不影响

这种方案需要依赖类型检查器对Protocol的支持,且不同工具的表现可能不一致,因此优先推荐方案一。

内容的提问来源于stack exchange,提问作者Jasmijn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:30:40