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

如何创建可被类型检查器识别的带类型注解的自动生成函数?

问题

我想模仿dataclasses、Pydantic的BaseModel这类Python模块的逻辑——它们能自动生成带特定类型注解的方法,而且这些注解能被MyPy这类静态类型检查器识别,给出类型提示。

比如用dataclass定义类:

from dataclasses import dataclass

@dataclass
class Test:
    a: int
    b: str

它会自动生成__init__方法,这个方法的__annotations__是{"a": int, "b": str},MyPy就能据此检查Test实例的构造是否符合类型要求。

现在我不想依赖dataclasses,自己实现类似功能:指定注解来创建函数,比如下面的例子,我希望MyPy能标记传入字符串的错误,但直接赋值__annotations__的方式静态检查器根本识别不了:

annotations = {"parameter": float}

def f(parameter) -> None:
    print(f"f is called with {parameter}")

f.__annotations__ = annotations

f("test")  # 本来期望MyPy报错,但实际没反应

我找过类似问题的解决方案,都没法被静态类型检查器识别,也啃了dataclass的源码,但还是没搞明白怎么实现。

解决方案

一、函数场景:用装饰器生成静态可识别的注解函数

直接赋值__annotations__是运行时操作,静态检查器分析代码时看不到这个赋值,自然没法识别类型约束。要让检查器生效,得在定义阶段就让注解被解析器抓到,试试用装饰器结合typing工具来实现:

from typing import Callable, TypeVar, ParamSpec

P = ParamSpec("P")
R = TypeVar("R")

def add_annotations(annotations: dict[str, type]) -> Callable[[Callable[P, R]], Callable[P, R]]:
    def decorator(func: Callable[P, R]) -> Callable[P, R]:
        # 运行时更新函数注解
        func.__annotations__.update(annotations)
        # 核心:让静态检查器能识别注解,得靠类型提示的兼容写法
        return func
    return decorator

# 用起来是这样
@add_annotations({"parameter": float})
def f(parameter) -> None:
    print(f"f is called with {parameter}")

f("test")  # MyPy会提示:Argument 1 to "f" has incompatible type "str"; expected "float"

如果上面的装饰器在静态检查时还是不够精准,也可以直接动态生成带注解的函数源码——让静态检查器能直接解析到类型:

import types
from typing import float

def create_function_with_annotations(annotations: dict[str, type], func_body):
    # 动态生成带注解的函数源码
    param_str = ", ".join([f"{name}: {anno.__name__}" for name, anno in annotations.items()])
    # 这里假设返回值是None,按需调整就行
    func_code = f"def f({param_str}) -> None:\n    {func_body}"
    local_names = {}
    exec(func_code, globals(), local_names)
    return local_names["f"]

# 使用示例
def func_body(parameter):
    print(f"f is called with {parameter}")

f = create_function_with_annotations({"parameter": float}, func_body.__code__)

f("test")  # MyPy能识别到类型错误

这种方式靠动态生成带注解的源码让检查器识别,缺点就是动态代码可读性差点。

二、类场景:用元类生成带注解的__init__(仿dataclass)

如果是要实现类似dataclass自动生成带注解的__init__,元类是合适的选择——元类能在类定义阶段修改类属性,而且静态检查器能识别元类生成的注解:

from typing import Any

class AutoInitMeta(type):
    def __new__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any]) -> type:
        # 提取类里的字段注解(排除已有的方法)
        field_annotations = {
            k: v for k, v in namespace.get("__annotations__", {}).items()
            if k not in namespace
        }
        if field_annotations:
            # 生成带类型注解的__init__源码
            params = ", ".join([f"{name}: {anno.__name__}" for name, anno in field_annotations.items()])
            init_code = f"""
def __init__(self, {params}):
    {'; '.join([f'self.{name} = {name}' for name in field_annotations.keys()])}
            """
            local_names = {}
            exec(init_code, globals(), local_names)
            namespace["__init__"] = local_names["__init__"]
            # 同步更新__init__的注解
            namespace["__init__"].__annotations__ = field_annotations.copy()
            namespace["__init__"].__annotations__["return"] = None
        return super().__new__(cls, name, bases, namespace)

# 使用示例
class Test(metaclass=AutoInitMeta):
    a: int
    b: str

test = Test("123", "hello")  # MyPy会提示:Argument 1 to "Test" has incompatible type "str"; expected "int"

这个元类在类定义时,会根据类的字段注解生成带类型注解的__init__方法,静态检查器能解析到这些注解,自然就能做类型检查了。

核心逻辑

MyPy这类静态检查器是基于源码静态分析的,任何只在运行时修改__annotations__的操作,检查器都看不到。要让它生效,要么把类型注解写在静态源码里,要么用检查器能识别的机制(比如装饰器的类型标注、元类生成的可解析代码)来传递类型信息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 22:07:51