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

如何在Python TypedDict中指定任意额外字段?

Python TypedDict 实现必填固定字段+额外任意字段

需求场景

需要定义TypedDict,同时指定必填的固定字段和允许任意额外字段,用于Flask装饰器的类型标注:Flask路由装饰器会将路径参数作为关键字参数传递,装饰器需要提取并处理其中一个参数(如thing_id),再将转换后的参数(如thing)和剩余参数传递给被装饰函数。

当前代码示例(示意)

from typing import Any, Callable, ParamSpec, Tuple, TypedDict, TypeVar

from myproject.models import Thing

P0 = ParamSpec("P0")
P1 = ParamSpec("P1")
R = TypeVar("R")

# 无效写法,仅作示意
class IncomingKwargs(TypedDict):
    thing_id: str
    **rest

class ForwardedKwargs(TypedDict):
    thing: Thing
    **rest

Decoratee = Callable[P0, R]
Decorated = Callable[P1, R]

def with_thing() -> Callable[[Decoratee], Decorated]:
    def decorator(f: Decoratee) -> Decorated:
        def wrapper(*args: Any, **kwargs: IncomingKwargs) -> R:
            thing = Thing.from_id(kwargs["thing_id"])   
            return f(*args, thing=thing, **kwargs["rest"])
        return wrapper
    return decorator

# Flask使用示例
@app.route("/things/:thing_id/frobnicate", method=["POST"])
@with_thing()
def frobnicate_thing(thing: Thing) -> Tuple[str, int]:
    return "Thing was frobnicated.", 200

遇到的问题

  • 查阅Python官方及mypy文档,未找到直接实现该需求的示例
  • 让TypedDict继承dict无法通过类型检查,也无法正确传递额外字段
  • TypedDict的total=False选项不符合需求,因为需要thing_id为必填字段

TypeScript 参考实现

在TypeScript中可以通过索引签名轻松实现:

type IncomingKwargs = {
  thing_id: string,
  [key: string]: any,
}

const someKwargs: IncomingKwargs = {thing_id: "12345", description: "A beautiful thing"}

// 解构提取必填字段和剩余字段
const { thing_id, ...rest } = someKwargs

解决方案

方案1:使用mypy扩展特性 extra="allow"(推荐)

mypy从0.910版本开始支持TypedDict的extra="allow"参数,允许在保留必填固定字段的同时,接受任意额外字段:

from typing import TypedDict, ParamSpec, TypeVar, Callable, Tuple
from typing_extensions import Concatenate, Unpack

from myproject.models import Thing

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

# 定义允许额外字段的TypedDict,thing_id为必填
class IncomingKwargs(TypedDict, extra="allow"):
    thing_id: str

class ForwardedKwargs(TypedDict, extra="allow"):
    thing: Thing

# 类型别名:被装饰函数需要接受Thing和其他参数
Decoratee = Callable[Concatenate[Thing, P], R]
# 类型别名:装饰后函数接受thing_id和其他参数
Decorated = Callable[Concatenate[str, P], R]

def with_thing() -> Callable[[Decoratee], Decorated]:
    def decorator(f: Decoratee) -> Decorated:
        def wrapper(*args: Unpack[P], thing_id: str, **kwargs: Any) -> R:
            thing = Thing.from_id(thing_id)
            # 传递转换后的参数和所有剩余参数
            return f(thing, *args, **kwargs)
        return wrapper
    return decorator

# Flask使用修正(注意路由参数写法和methods复数)
@app.route("/things/<thing_id>/frobnicate", methods=["POST"])
@with_thing()
def frobnicate_thing(thing: Thing) -> Tuple[str, int]:
    return "Thing was frobnicated.", 200

方案2:标准库兼容写法(无需mypy扩展)

如果不想依赖mypy扩展,可以在函数参数中分开标注必填字段和额外关键字参数,类型检查器同样能识别必填项:

from typing import ParamSpec, TypeVar, Callable, Tuple
from typing_extensions import Concatenate, Unpack

from myproject.models import Thing

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

Decoratee = Callable[Concatenate[Thing, P], R]
Decorated = Callable[Concatenate[str, P], R]

def with_thing() -> Callable[[Decoratee], Decorated]:
    def decorator(f: Decoratee) -> Decorated:
        def wrapper(*args: Unpack[P], thing_id: str, **kwargs: Any) -> R:
            thing = Thing.from_id(thing_id)
            return f(thing, *args, **kwargs)
        return wrapper
    return decorator

注意事项

  • 方案1的extra="allow"是mypy专属特性,需确保mypy版本≥0.910
  • 使用Concatenate和Unpack需要Python 3.11+,或安装typing_extensions库兼容旧版本
  • Flask路由参数的正确写法是<thing_id>而非:thing_id,methods参数需用复数形式

内容的提问来源于stack exchange,提问作者t-r0d

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 00:44:57