Python打字:打字件是否允许其他 /额外的密钥?

发布于 2025-01-20 16:45:03 字数 80 浏览 1 评论 0原文

typing.typeddict允许额外的密钥吗?如果typechecker的键在TypedDict的定义上不存在?

Does typing.TypedDict allow extra keys? Does a value pass the typechecker, if it has keys which are not present on the definition of the TypedDict?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

海夕 2025-01-27 16:45:04

如果您知道可选键,则可以使用 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"

If you know the optionals keys, you can use 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"
我不咬妳我踢妳 2025-01-27 16:45:03

这取决于。

PEP-589,TypedDict 规范, 明确禁止额外的键:

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

m: 电影 = dict(
      name='外星人',
      年=1979,
      director='Ridley Scott') # 错误:意外的密钥 'director'

[我强调]

类型检查器 mypypyrepyright 根据规范实现这一点。

然而,有可能接受带有额外键的值。这是因为 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

It depends.

PEP-589, the specification of TypedDict, explicitely forbids extra keys:

Extra keys included in TypedDict object construction should also be caught. In this example, the director key is not defined in Movie and is expected to generate an error from a type checker:

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

[emphasis by me]

The typecheckers mypy, pyre, and pyright implement this according to the specification.

However, it is possible that a value with extra keys is accepted. This is because subtyping of TypedDicts is allowed, and the subtype might implement the extra key. PEP-589 only forbids extra keys in object construction, i.e. in literal assignment. As any value that complies with a subtype is always deemed to comply with the parent type and can be upcasted from the subtype to the parent type, an extra key can be introduced through a subtype:

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  

In the example above, we see that the same value can sometimes be considered complying with Movie by the typing system, and sometimes not.

As a consequence of subtyping, typing a parameter as a certain TypedDict is not a safeguard against extra keys, because they could have been introduced through a subtype.

If your code is sensitive with regard to the presence of extra keys (for instance, if it makes use of param.keys(), param.values() or len(param) on the TypedDict parameter param), this could lead to problems when extra keys are present. A solution to this problem is to either handle the exceptional case that extra keys are actually present on the parameter or to make your code insensitive against extra keys.

If you want to test that your code is robust against extra keys, you cannot simply add a key in the test value:

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',
    })    

Workarounds are to either make the type checkers ignore the line or to create a subtype for your test, introducing the extra keys only for your test:

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
心意如水 2025-01-27 16:45:03

Python TypedDict 尚不支持额外的项目,可能会在 python3.13 中支持。
请参阅 PEP728

Python TypedDict does not support extra items yet and may be supported in python3.13 .
See in PEP728

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文