为什么只读映射不能用作 Python 中 Dict 属性的类型提示?

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

为什么只读

Mapping
不能用作
Dict
属性的类型提示? 我知道
dict
是可变的,这使得
field
不变,但是你能解释一下将它传递给只读
Mapping
类型会出现什么问题吗?

考虑以下代码:

from typing import Dict, Mapping, Protocol

class A:
    field: Dict

class B(Protocol):
    field: Mapping

def f(arg: B):
    print(arg)

f(A())

此代码将在调用 pyright 中的

f(A())
时引发类型错误:

Argument of type "A" cannot be assigned to parameter "arg" of type "B" in function "f"
  "A" is incompatible with protocol "B"
    "field" is invariant because it is mutable
    "field" is an incompatible type
      "Dict[Unknown, Unknown]" is incompatible with "Mapping[Unknown, Unknown]"

在 mypy 中:

error: Argument 1 to "f" has incompatible type "A"; expected "B"  [arg-type]
note: Following member(s) of "A" have conflicts:
note:     field: expected "Mapping[Any, Any]", got "Dict[Any, Any]"

这是为什么?

B protocol
定义类型为
Mapping
的属性字段,它应该包括可变和不可变映射。然而,
A
类定义了一个
Dict
类型的属性字段,一个可变的映射对象。

不应该只读

Mapping
代替
Dict
对象,因为它只提供对映射对象的只读访问?

python mapping mypy typing pyright
1个回答
0
投票

问题中的代码确实不是类型安全的,类型检查器的建议是正确的。

如果你有一个注释为

Mapping
的参数,你可以给它分配任何你想要的东西——它不会影响调用者的传递值。当您传递任何可变或嵌套结构 containing a
Mapping
时,您仍然应该被允许为该项目/字段分配任何内容。让我们看看以下内容:

from collections import Counter
from typing import Dict, Mapping, Protocol


class B(Protocol):
    field: Mapping[str, int]


class A:
    field: Dict[str, int]


def f(arg: B) -> None:
    arg.field = Counter()
    print(arg)


a = A()
f(a)
assert isinstance(a.field, dict)  # Oops

从打字的角度来看,

f
是否正确?是的,它得到了带有
Mapping
字段的东西,并用另一个
Mapping
替换了它,没什么好担心的。嗯?但是
f
的调用者传递了
A
实例,并且可能仍然(正确地)期望
field
值仍然是
dict
。如果类型检查器保持沉默,则不会注意到此错误。

为避免此警告,您可以直接对协议进行子类化,将

A
类标记为实现者。这是类型系统中一个微妙的地方,很难解释(可能是为了简单而交换严格),因为你的实现仍然会失败并出现同样的错误,但没有类型检查器的担忧。通常的类也会发生同样的情况:子类可以覆盖具有更具体字段子类型的字段。

这是一个 playground 有更多的代码可以玩。

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