Python 打字:TypedDict 是否允许附加/额外的键?

问题描述 投票:0回答:3

typing.TypedDict
允许额外的钥匙吗?如果某个值具有 TypedDict 定义中不存在的键,它是否会通过类型检查器?

python mypy python-typing
3个回答
12
投票

这要看情况。

PEP-589,

TypedDict
的规范明确禁止额外的按键:

TypedDict 对象构造中包含的额外键也应该被捕获。在此示例中,导演键未在 Movie 中定义,预计会从类型检查器中生成错误:

m: Movie = dict(
      name='Alien',
      year=1979,
      director='Ridley Scott')  # error: Unexpected key 'director'

[我突出显示]

类型检查器 mypy、pyre、pyright 根据规范实现此功能。

但是,有可能接受带有额外键的值。这是因为 TypedDicts 的子类型是允许的,并且子类型可能实现额外的键。 PEP-589 仅禁止在对象构造中(即文字赋值中)使用额外的键。由于任何符合子类型的值始终被视为符合父类型,并且可以从子类型向上转换为父类型,因此可以通过子类型引入额外的键:

from typing import TypedDict

class Movie(TypedDict):
    name: str
    year: int

class MovieWithDirector(Movie):
    director: str


# This is illegal:
movie: Movie = {
    'name': 'Ash is purest white', 
    'year': 2018, 
    'director': 'Jia Zhangke',
}    

# This is legal:
movie_with_director: MovieWithDirector = {
    'name': 'Ash is purest white', 
    'year': 2018, 
    'director': 'Jia Zhangke',
}

# This is legal, MovieWithDirector is a subtype of Movie
movie: Movie = movie_with_director  

在上面的示例中,我们看到相同的值有时可以被打字系统视为符合

Movie
,有时则不然。

作为子类型的结果,将参数键入为某个 TypedDict 并不能防止额外的键,因为它们可能是通过子类型引入的。

如果您的代码对额外键的存在很敏感(例如,如果它在

param.keys()
参数
param.values()
上使用
len(param)
TypedDict
param
),这可能会导致问题当有额外的钥匙时。此问题的解决方案是处理参数上实际存在额外键的特殊情况,或者使代码对额外键不敏感。

如果您想测试您的代码对于额外键的鲁棒性,您不能简单地在测试值中添加键:

def some_movie_function(movie: Movie):
    # ...

def test_some_movie_function():
    # this will not be accepted by the type checker:
    result = some_movie_function({
        'name': 'Ash is purest white', 
        'year': 2018, 
        'director': 'Jia Zhangke',
        'genre': 'drama',
    })    

解决方法是让类型检查器忽略该行,或者为您的测试创建一个子类型,仅为您的测试引入额外的键:

class ExtendedMovie(Movie):
     director: str
     genre: str


def test_some_movie_function():
    extended_movie: ExtendedMovie = {
        'name': 'Ash is purest white', 
        'year': 2018, 
        'director': 'Jia Zhangke',
        'genre': 'drama',
    }

    result = some_movie_function(test_some_movie_function)
    # run assertions against result
} 

2
投票

如果您知道可选键,则可以使用

total=False
:

class Movie(TypedDict):
    name: str
    year: int


class MovieWithDirector(Movie, total=False):
    director: str


x: MovieWithDirector = {'name':''}  # error: missing "year"
y: MovieWithDirector = {'name':'', 'year':1}  # ok
z: MovieWithDirector = {'name':'', 'year':1, 'director':''}  # ok
w: MovieWithDirector = {'name':'', 'year':1, 'foo':''}  # error key "foo"

0
投票

Python dict 尚不支持额外项目,可能会在 python3.13 中支持。 参见PEP728

© www.soinside.com 2019 - 2024. All rights reserved.