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

如何约束可变参数可调用对象的参数均为JSON子类型?

解决memoize函数的参数JSON类型约束问题

静态类型检查的现状与替代方案

目前Python标准库的ParamSpec(截至3.12版本)不支持为参数集合设置统一的类型边界,没法直接通过memoize[**P: JSON, T: JSON](...)这种语法约束所有参数为JSON子类型。同时issubclass在静态类型检查阶段无法与类型变量兼容,因为此时类型变量是抽象的,无法做实际类型判断。

替代方案:用Protocol定义合规Callable

可以通过Protocol定义一个仅接受JSON类型参数的调用协议,让memoize的入参函数符合该协议:

from typing import Protocol, Callable, TypeVar, Union, Mapping, Sequence

JSON = Union[Mapping[str, "JSON"], Sequence["JSON"], str, int, float, bool, None]
T = TypeVar("T", bound=JSON)

class JSONCallable(Protocol):
    def __call__(self, *args: JSON, **kwargs: JSON) -> T: ...

def memoize(fn: JSONCallable[T]) -> JSONCallable[T]:
    # 实现memoize逻辑
    cache = {}
    def wrapper(*args: JSON, **kwargs: JSON) -> T:
        key = (args, frozenset(kwargs.items()))
        if key not in cache:
            cache[key] = fn(*args, **kwargs)
        return cache[key]
    return wrapper

局限:该方案会丢失原函数的精确参数签名(比如参数名称、个数),静态检查时无法提示原函数的参数细节。

运行时参数类型检查实现

如果静态约束无法满足需求,可以在memoize内部创建包装器前,通过解析函数签名的类型注解做运行时检查:

import inspect
from typing import Union, Mapping, Sequence, Callable, ParamSpec, TypeVar, get_args, get_origin

JSON = Union[Mapping[str, "JSON"], Sequence["JSON"], str, int, float, bool, None]
P = ParamSpec("P")
T = TypeVar("T", bound=JSON)

def is_json_type(tp) -> bool:
    """判断给定类型是否为JSON的子类型"""
    # 处理Union类型
    if get_origin(tp) is Union:
        return all(is_json_type(arg) for arg in get_args(tp))
    # 处理泛型容器类型
    origin = get_origin(tp)
    if origin is not None:
        if origin is Mapping:
            key_tp, val_tp = get_args(tp)
            return key_tp is str and is_json_type(val_tp)
        if origin is Sequence:
            elem_tp, = get_args(tp)
            return is_json_type(elem_tp)
    # 处理基础类型与自定义子类
    return tp in (str, int, float, bool, type(None)) or issubclass(tp, (str, int, float, bool))

def memoize(fn: Callable[P, T]) -> Callable[P, T]:
    # 运行时验证所有参数类型
    sig = inspect.signature(fn)
    for param in sig.parameters.values():
        param_tp = param.annotation
        if param_tp is inspect.Parameter.empty:
            raise TypeError(f"参数 {param.name} 未提供类型注解,无法验证是否为JSON子类型")
        if not is_json_type(param_tp):
            raise TypeError(f"参数 {param.name} 的类型 {param_tp} 不属于JSON子类型")
    
    # 标准memoize包装逻辑
    cache = {}
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
        key = (args, frozenset(kwargs.items()))
        if key not in cache:
            cache[key] = fn(*args, **kwargs)
        return cache[key]
    return wrapper

运行时检查注意事项

  • 无类型注解的参数会直接抛出错误,若需宽松处理可改为警告,或默认视为合规,但存在类型风险。
  • 支持泛型容器类型(如list[JSON]、dict[str, int])的检查。
  • 支持自定义JSON子类型(如继承str的自定义类)的检查。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 03:18:30