Python 3.9
包含
PEP 585
并弃用
typing
模块中的许多类型,转而支持
collections.abc
中的类型,现在它们支持
__class_getitem__
例如
Callable
就是这种情况。对我来说,
typing.Callable
和
collections.abc.Callable
的行为应该总是相似的,但事实并非如此。
这个简单的示例会导致错误:
>>> from typing import Optional
>>> from collections.abc import Callable
>>> def foo(arg: Optional[Callable[[int], int]]) -> None:
... pass
...
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/usr/local/lib/python3.9/typing.py", line 262, in inner
return func(*args, **kwds)
File "/usr/local/lib/python3.9/typing.py", line 339, in __getitem__
return self._getitem(self, parameters)
File "/usr/local/lib/python3.9/typing.py", line 463, in Optional
return Union[arg, type(None)]
File "/usr/local/lib/python3.9/typing.py", line 262, in inner
return func(*args, **kwds)
File "/usr/local/lib/python3.9/typing.py", line 339, in __getitem__
return self._getitem(self, parameters)
File "/usr/local/lib/python3.9/typing.py", line 451, in Union
parameters = _remove_dups_flatten(parameters)
File "/usr/local/lib/python3.9/typing.py", line 231, in _remove_dups_flatten
return tuple(_deduplicate(params))
File "/usr/local/lib/python3.9/typing.py", line 205, in _deduplicate
all_params = set(params)
TypeError: unhashable type: 'list'
但同样的错误不会发生在
typing.Callable
:| 中。 ||如果签名稍微简化一点,也不会发生该错误:
>>> from typing import Callable
>>> def foo(arg: Optional[Callable[[int], int]]) -> None:
... pass
...
>>> # no error
这是 Python 3.9 和 3.9.1 中的错误吗?
>>> from collections.abc import Callable
>>> def foo(arg: Optional[Callable[..., int]]) -> None:
... pass
...
>>> # no error
>>> def foo(arg: Callable[[int], int]) -> None:
... pass
...
>>> # no error
Is this a bug in Python 3.9 and 3.9.1?
这不是 Python 中的错误,而是使用
collections.abc.Callable
时的一个微妙问题,与
typing.Callable
相比。虽然 PEP 585 旨在使内置类型与来自
typing
的类型可互换用于类型提示,但在这种情况下,交互的复杂性会导致行为上的差异。
以下是发生错误的原因:
-
typing.Callable
是一个特殊的类型提示。 它被 Python 类型检查器特殊处理,并理解如何以类型提示的方式处理其参数。 -
collections.abc.Callable
是一个抽象基类。 它旨在用于运行时检查对象是否可调用,但它没有typing.Callable
的特殊类型提示行为。 -
当执行
Optional[Callable[[int], int]]
时,会发生类型嵌套。Optional[x]
只是Union[x, None]
的简写。 问题出在Union
试图“规范化”其参数,这涉及到迭代和散列它们。 -
不能对
list
进行散列。 当Union
试图规范化具有collections.abc.Callable[[int], int]
的类型时,它将[int]
视为一个列表(因为它实际上是一个列表),并尝试对其进行散列,从而导致TypeError
。
解决方案:
-
坚持使用
typing.Callable
进行类型提示。 这是解决此类问题的推荐方法。typing.Callable
旨在用于类型提示,并且由类型检查器专门处理。 -
如果出于某种原因需要使用
collections.abc.Callable
,请避免在需要对其进行散列的上下文中使用它(例如,在Union
中)。
请记住,即使在 Python 3.9+ 中,
typing
模块对于类型提示仍然很重要,而且在许多情况下,其行为与
collections.abc
中的对应物不完全相同。