经过严格检查的 VSCode Pylance v2023.9.20 在第一个函数调用时给我类型错误,但在第二个函数调用时却没有。唯一的区别是第一个参数包含在列表中。
def listTupleFunc(arg: list[tuple[str, tuple[str, ...]]]) -> None:
pass
listTuple = [('x', ('a', 'b'))]
listTupleFunc(listTuple) # Error here
def tupleOnlyFunc(arg: tuple[str, tuple[str, ...]]) -> None:
pass
tupleOnly = ('x', ('a', 'b'))
tupleOnlyFunc(tupleOnly) # OK
错误信息是:
Argument of type "list[tuple[Literal['x'], tuple[Literal['a'], Literal['b']]]]" cannot be assigned to parameter "arg" of type "list[tuple[str, tuple[str, ...]]]" in function "listTupleFunc"
"list[tuple[Literal['x'], tuple[Literal['a'], Literal['b']]]]" is incompatible with "list[tuple[str, tuple[str, ...]]]"
Type parameter "_T@list" is invariant, but "tuple[Literal['x'], tuple[Literal['a'], Literal['b']]]" is not the same as "tuple[str, tuple[str, ...]]"
如果我进行更改,以便对文字的some进行格式化,令人惊讶的是,错误消失了:
# These work OK somehow
listTuple = [(f'x', ('a', f'b'))]
listTuple = [(f'x', (f'a', 'b'))]
# But these don't
listTuple = [('x', (f'a', f'b'))]
listTuple = [(f'x', ('a', 'b'))]
这是 Pylance 中的错误还是我遗漏了一些明显的东西?如何消除错误?
您可以将
listTuple
转换为所需类型。
def listTupleFunc(arg: list[tuple[str, tuple[str, ...]]]) -> None:
pass
listTuple: list[tuple[str, tuple[str, ...]]] = [('x', ('a', 'b'))]
listTupleFunc(listTuple) # No error!
def tupleOnlyFunc(arg: tuple[str, tuple[str, ...]]) -> None:
pass
tupleOnly = ('x', ('a', 'b'))
tupleOnlyFunc(tupleOnly) # OK
这将消除错误。 PyLance 预计这些硬编码文字与您使用的一般定义不相容。
因此,错误消息非常具有描述性。当你这样做时:
listTuple = [('x', ('a', 'b'))]
然后pyright infer的类型推断规则:
list[tuple[Literal['x'], tuple[Literal['a'], Literal['b']]]]
现在,您可能会说,“但是
tuple[Literal['x'], tuple[Literal['a'], Literal['b']]
是 tuple[str, tuple[str, ...]]
的子类型,这里应该接受它!
但是看看错误消息的其余部分,它指出,
list
是不变。因此,如果您有 list[T]
,并且 list[S]
并且 S
是 T
的子类型,那么 list[S]
并不是 list[T]
! 的子类型,正如您所期望的
人们倾向于期望参数化类型是协变。
list
对象必须保持不变,因为它们是可变的(在 PEP 484 中了解更多相关信息)。
考虑以下玩具示例:
class T:
pass
class S(T):
pass
s = S() # inferring type S
reveal_type(s)
t: T = s # OK!
data_s = [S(), S()] # inferring type list[s]
reveal_type(data_s)
data_t: list[T] = data_s # Whoops!
这是
pyright
在我的机器上显示的内容:
jarrivillaga-mbp16-2019:scratch jarrivillaga$ pyright test_typing.py
No configuration file found.
No pyproject.toml file found.
stubPath /Users/jarrivillaga/scratch/typings is not a valid directory.
Assuming Python platform Darwin
Searching for source files
Found 1 source file
/Users/jarrivillaga/scratch/test_typing.py
/Users/jarrivillaga/scratch/test_typing.py:8:13 - info: Type of "s" is "S"
/Users/jarrivillaga/scratch/test_typing.py:13:13 - info: Type of "data_s" is "list[S]"
/Users/jarrivillaga/scratch/test_typing.py:15:20 - error: Expression of type "list[S]" cannot be assigned to declared type "list[T]"
TypeVar "_T@list" is invariant
"S" is incompatible with "T" (reportGeneralTypeIssues)
1 error, 0 warnings, 2 infos
Completed in 0.495sec
因此,一种解决方案是使用超类型显式注释变量,并且不依赖类型推断,在玩具示例中:
data_s: list[T] = [S(), S()] # don't rely on inference!
或者您的代码:
listTuple: tuple[str, tuple[str, ...]] = [('x', ('a', 'b'))]
另一种选择,如果您不需要依赖任何 mutator 方法来实现
list
(例如 .append
或 .pop
或 mlist[i] = whatever
),那么您可以使用 collections.abc.Sequence
,即 协变:
from collections.abc import Sequence
class T:
pass
class S(T):
pass
s = S() # inferring type S
reveal_type(s)
t: T = s # OK!
data_s = [S(), S()] # inferring type list[s]
reveal_type(data_s)
data_t: Sequence[T] = data_s # covariance, yay!
现在请注意:
jarrivillaga-mbp16-2019:scratch jarrivillaga$ pyright test_typing.py
No configuration file found.
No pyproject.toml file found.
stubPath /Users/jarrivillaga/scratch/typings is not a valid directory.
Assuming Python platform Darwin
Searching for source files
Found 1 source file
/Users/jarrivillaga/scratch/test_typing.py
/Users/jarrivillaga/scratch/test_typing.py:10:13 - info: Type of "s" is "S"
/Users/jarrivillaga/scratch/test_typing.py:15:13 - info: Type of "data_s" is "list[S]"
0 errors, 0 warnings, 2 infos
Completed in 0.489sec