PEP 764 – 内联类型字典
- 作者:
- Victorien Plot <contact at vctrn.dev>
- 发起人:
- Eric Traut <erictr at microsoft.com>
- 讨论至:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 主题:
- 类型标注
- 创建日期:
- 2024年10月25日
- Python 版本:
- 3.15
- 发布历史:
- 2025年1月29日
摘要
PEP 589 定义了基于类和函数式语法来创建类型字典。在这两种情况下,它都要求定义一个类或赋值给一个值。在某些情况下,这会增加不必要的样板代码,尤其是当类型字典只使用一次时。
本PEP提议通过对TypedDict
类型进行下标操作来添加一种新的内联语法
from typing import TypedDict
def get_movie() -> TypedDict[{'name': str, 'year': int}]:
return {
'name': 'Blade Runner',
'year': 1982,
}
动机
Python字典是语言的基本数据结构。很多时候,它用于在函数中返回或接受结构化数据。然而,定义TypedDict
类可能会很繁琐
- 类型字典需要一个可能不相关的名称。
- 嵌套字典需要多个类定义。
以一个返回某些嵌套结构化数据的简单函数为例
from typing import TypedDict
class ProductionCompany(TypedDict):
name: str
location: str
class Movie(TypedDict):
name: str
year: int
production: ProductionCompany
def get_movie() -> Movie:
return {
'name': 'Blade Runner',
'year': 1982,
'production': {
'name': 'Warner Bros.',
'location': 'California',
}
}
基本原理
新的内联语法可以用来解决这些问题
def get_movie() -> TypedDict[{'name': str, 'year': int, 'production': TypedDict[{'name': str, 'location': str}]}]:
...
虽然不那么有用(因为可以使用函数式甚至基于类的语法),但内联类型字典可以作为一个别名赋值给一个变量
InlineTD = TypedDict[{'name': str}]
def get_movie() -> InlineTD:
...
规范
TypedDict
特殊形式被设计为可下标的,并接受一个单一的类型参数,该参数必须是 dict
,遵循与函数式语法相同的语义(字典键是表示字段名称的字符串,值是有效的注解表达式)。只允许使用括号内逗号分隔的 key: value
对构造函数({k: <type>}
),并且应直接指定为类型参数(即不允许使用先前已赋值为 dict
实例的变量)。
内联类型字典可以被称为匿名的,这意味着它们没有特定的名称(参见运行时行为部分)。
可以定义一个嵌套的内联字典
Movie = TypedDict[{'name': str, 'production': TypedDict[{'location': str}]}]
# Note that the following is invalid as per the updated `type_expression` grammar:
Movie = TypedDict[{'name': str, 'production': {'location': str}}]
虽然不能指定任何类参数,例如 total
,但任何类型修饰符都可以用于单个字段
Movie = TypedDict[{'name': NotRequired[str], 'year': ReadOnly[int]}]
内联类型字典默认是完整的,这意味着所有键都必须存在。Required
类型修饰符的使用因此是冗余的。
类型变量允许在内联类型字典中使用,前提是它们绑定到某个外部作用域
class C[T]:
inline_td: TypedDict[{'name': T}] # OK, `T` is scoped to the class `C`.
reveal_type(C[int]().inline_td['name']) # Revealed type is 'int'
def fn[T](arg: T) -> TypedDict[{'name': T}]: ... # OK: `T` is scoped to the function `fn`.
reveal_type(fn('a')['name']) # Revealed type is 'str'
type InlineTD[T] = TypedDict[{'name': T}] # OK, `T` is scoped to the type alias.
T = TypeVar('T')
InlineTD = TypedDict[{'name': T}] # OK, same as the previous type alias, but using the old-style syntax.
def func():
InlineTD = TypedDict[{'name': T}] # Not OK: `T` refers to a type variable that is not bound to the scope of `func`.
内联类型字典可以被扩展
InlineTD = TypedDict[{'a': int}]
class SubTD(InlineTD):
pass
类型规范变更
内联类型字典引入了一种新的类型表达式。因此,type_expression
生产规则将更新以包含内联语法
new-type_expression ::=type_expression
| <TypedDict> '[' '{' (string: ':'annotation_expression
',')* '}' ']' (where string is any string literal)
运行时行为
创建一个内联类型字典会产生一个新的类,因此 T1
和 T2
属于同一类型
from typing import TypedDict
T1 = TypedDict('T1', {'a': int})
T2 = TypedDict[{'a': int}]
由于内联类型字典旨在是匿名的,它们的__name__
属性将被设置为 <inline TypedDict>
字符串字面量。未来,可以添加一个显式的类属性,以便将它们与命名类区分开来。
虽然TypedDict
被文档化为一个类,但它的定义方式是一个实现细节。实现将不得不进行调整,以便TypedDict
可以被下标。
向后兼容性
本PEP不引入任何向后不兼容的更改。
安全隐患
本 PEP 没有已知的安全隐患。
如何教授此内容
新的内联语法将在typing
模块文档和类型规范中进行文档说明。
当使用复杂的字典结构时,将所有内容定义在一行上可能会影响可读性。代码格式化工具可以通过将内联类型字典格式化为多行来提供帮助
def edit_movie(
movie: TypedDict[{
'name': str,
'year': int,
'production': TypedDict[{
'location': str,
}],
}],
) -> None:
...
参考实现
Mypy 支持类似的语法作为实验性 特性
def test_values() -> {"int": int, "str": str}:
return {"int": 42, "str": "test"}
对本 PEP 的支持已在 此拉取请求中添加。
Pyright 在版本 1.1.387 中添加了对新语法的支持。
运行时实现
必要的更改首先在 typing_extensions 的 此拉取请求中实现。
被拒绝的想法
在注解中使用函数式语法
可直接将替代的函数式语法用作注解
def get_movie() -> TypedDict('Movie', {'title': str}): ...
然而,目前在这样的上下文中,由于各种原因(处理开销大,评估不标准化),函数调用表达式是不受支持的。
这也需要一个有时不相关的名称。
使用带单个类型参数的 dict
或 typing.Dict
我们可以重用带单个类型参数的dict
或typing.Dict
来表达相同的概念
def get_movie() -> dict[{'title': str}]: ...
使用简单的字典
不使用 TypedDict
类进行下标操作,而是一个普通的字典可以作为注解使用
def get_movie() -> {'title': str}: ...
然而,PEP 584 为字典添加了联合运算符,并且 PEP 604 引入了联合类型。这两个特性都使用了按位或 (|) 运算符,使得以下用例不兼容,尤其是在运行时自省方面
# Dictionaries are merged:
def fn() -> {'a': int} | {'b': str}: ...
# Raises a type error at runtime:
def fn() -> {'a': int} | int: ...
扩展其他类型字典
可以使用几种语法来扩展其他类型字典
InlineBase = TypedDict[{'a': int}]
Inline = TypedDict[InlineBase, {'b': int}]
# or, by providing a slice:
Inline = TypedDict[{'b': int} : (InlineBase,)]
由于内联类型字典旨在仅支持现有语法的一个子集,考虑到所增加的复杂性,添加此扩展机制并不足以获得支持。
如果交集要被添加到类型系统中,它可以涵盖这个用例。
未解决的问题
内联类型字典和额外项
PEP 728 引入了封闭类型字典的概念。如果本 PEP 被接受,内联类型字典将默认为封闭。这意味着PEP 728 需要首先处理,以便本 PEP 可以相应更新。
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源: https://github.com/python/peps/blob/main/peps/pep-0764.rst
最后修改: 2025-05-06 22:54:17 GMT