当前位置:首页>python>TypedDict 非要起名字?Python 团队:不,你不想.

TypedDict 非要起名字?Python 团队:不,你不想.

  • 2026-09-02 19:16:04
TypedDict 非要起名字?Python 团队:不,你不想.

定义一个函数,返回一个字典:

defget_movie():return {'name': 'Blade Runner','year': 1982,'production': {'name': 'Warner Bros.','location': 'California'        }    }

代码能跑。但你的类型检查器(比如 mypy、pyright)不知道里面有什么字段。它只知道你返回了一个 dict,键是字符串,值是混合类型。

这会导致两个问题:

  • 调用方不知道这个字典里有哪些键、每个键是什么类型。
  • 你写错了键名(比如把 year 写成 yeer),工具也抓不到。

Python 早就给了解决方案:「TypedDict」。

TypedDict 很好,但有点啰嗦

用 TypedDict 你得先定义一个类:

from typing import TypedDictclassProductionCompany(TypedDict):    name: str    location: strclassMovie(TypedDict):    name: str    year: int    production: ProductionCompanydefget_movie() -> Movie:return {'name': 'Blade Runner','year': 1982,'production': {'name': 'Warner Bros.','location': 'California'        }    }

这段代码很清楚。但有两个问题:

  1. 「命名负担」:ProductionCompany、Movie 这些名字有时候并不重要,你只是想描述“这个函数返回什么形状的数据”。
  2. 「嵌套很麻烦」:嵌套一层就得再定义一个类。如果数据结构有五六层,类定义比业务逻辑还长。

说实话,挺蠢的。让 AI 写我也嫌费 token。

PEP 764 要解决的就是这个问题

提案的核心想法很简单:「让你在类型注解里直接写出字典的结构」。

对比一下。之前你得写:

defget_movie() -> Movie:# 需要提前定义 Movie 类    ...

新语法允许你直接写:

from typing import TypedDictdefget_movie() -> TypedDict[{'name': str, 'year': int}]:return {'name': 'Blade Runner','year': 1982,    }

看懂了吗?

TypedDict[{'name': str, 'year': int}] 这个表达式本身就是一个类型。你不需要给它起名字,不需要单独定义类。

嵌套的情况更明显

回到最开始那个嵌套的例子。新语法下可以这样写:

defget_movie() -> TypedDict[{    'name': str,'year': int,'production': TypedDict[{'name': str,'location': str    }]}]:return {'name': 'Blade Runner','year': 1982,'production': {'name': 'Warner Bros.','location': 'California'        }    }

不用再定义 ProductionCompany 类了。结构直接写在注解里,一眼就能看出返回的字典长什么样。

这很紧凑。但说实话,这么写在一行上会很难读。提案里也提到了这个问题——代码格式化工具可以帮你自动拆成多行:

defedit_movie(    movie: TypedDict[{'name': str,'year': int,'production': TypedDict[{'location': str,        }],    }],) -> None:    ...

一些你可能关心的小细节

「1. 默认是 total(所有键都必须存在)」

这意味着你不能少键。如果你想让某个键可选,用 NotRequired:

TypedDict[{'name': str, 'year': NotRequired[int]}]

「2. 可以配合泛型使用」

deffn[T](arg: T) -> TypedDict[{'name': T}]:return {'name': arg}

调用 fn('hello') 后,类型检查器知道返回的字典里 name 字段是 str 类型。

「3. 内联的 TypedDict 是匿名的」

它的 __name__ 属性会被设为 "<inline TypedDict>"。如果你需要给类型起名字(比如多处复用),用原来的类定义方式更合适。

「4. 可以继承」

InlineTD = TypedDict[{'a': int}]classSubTD(InlineTD):    b: str

为什么要搞这么复杂?能不能直接用普通字典语法?

你可能想问:为什么不用 {'name': str} 直接表示类型?就像这样:

defget_movie() -> {'name': str, 'year': int}:  # 不行    ...

提案里讨论过这个想法,但被拒绝了。原因很实际:

Python 3.8 之后,{'a': int} | {'b': str} 这种写法已经被用来表示「字典的并集操作」(PEP 584)。如果再用它表示类型,就会产生歧义:

# 这到底是“返回一个字典,它的类型是 {'a': int} 或 {'b': str}”# 还是“返回两个字典的并集”?deffn() -> {'a': int} | {'b': str}: ...

运行时真的会去合并两个字典,然后报错。没法区分。

所以提案选择用 TypedDict[{...}] 这个新语法,不和现有功能打架。

什么时候能用?

PEP 764 目前是「草案」状态,目标 Python 版本是 「3.15」(预计 2027 年左右发布)。

但好消息是:

  • 「Pyright」 从 1.1.387 版本开始已经支持这个语法。
  • 「Mypy」 有一个实验性的类似语法(-> {"int": int, "str": str},不完全相同)。
  • typing_extensions 也有相关实现,可以提前尝鲜。

说白了就是:「当你只是想临时描述一个字典的形状,不想专门给它起个名字、单独定义一个类的时候,内联语法更省事。」

嵌套越深、一次性使用越多,新语法越划算。如果一个类型在五个函数里都用得到,还是老老实实定义类吧。

最新文章

随机文章