定义一个函数,返回一个字典:
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' } }
这段代码很清楚。但有两个问题:
- 「命名负担」:
ProductionCompany、Movie 这些名字有时候并不重要,你只是想描述“这个函数返回什么形状的数据”。 - 「嵌套很麻烦」:嵌套一层就得再定义一个类。如果数据结构有五六层,类定义比业务逻辑还长。
说实话,挺蠢的。让 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 也有相关实现,可以提前尝鲜。
说白了就是:「当你只是想临时描述一个字典的形状,不想专门给它起个名字、单独定义一个类的时候,内联语法更省事。」
嵌套越深、一次性使用越多,新语法越划算。如果一个类型在五个函数里都用得到,还是老老实实定义类吧。