如何在 Mypy 中使用 __subclasshook__?
How to use __subclasshook__ with Mypy?
为什么在 Mypy 下,__subclasshook__
适用于来自 collections.abc
的一招小马,但不适用于用户定义的 类?
比如这个程序
from collections.abc import Hashable
class A:
def __hash__(self) -> int:
return 0
a: Hashable = A()
产出
$ mypy demo.py --strict
Success: no issues found in 1 source file
但是这个等效程序
from abc import ABCMeta, abstractmethod
def _check_methods(C: type, *methods: str) -> bool:
mro = C.__mro__
for method in methods:
for B in mro:
if method in B.__dict__:
if B.__dict__[method] is None:
return NotImplemented
break
else:
return NotImplemented
return True
class Hashable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __hash__(self) -> int:
return 0
@classmethod
def __subclasshook__(cls, C: type) -> bool:
if cls is Hashable:
return _check_methods(C, "__hash__")
return NotImplemented
class A:
def __hash__(self) -> int:
return 0
a: Hashable = A()
产出
$ mypy demo.py --strict
demo.py:32: error: Incompatible types in assignment (expression has type "A", variable has type "Hashable")
Found 1 error in 1 file (checked 1 source file)
Mypy 是否以特殊方式处理一招小马?
是的,mypy
将这些 class 视为特殊情况。请记住,mypy
用于 static 类型检查,这意味着它无需 运行 您的代码即可工作,仅分析源代码。它实际上从未 调用 __subclasshook__
或类似的东西来确定什么是可散列的或不可散列的。您的“等效” class 仅在运行时等效,因为它依赖于 __subclasshook__
被调用。
如果你想 mypy
处理它不知道的东西,你必须写一个 mypy
plugin 来处理它。
Mypy 不使用标准库的实现,而是使用 typeshed 包中的规范(“存根文件”)。在这个包中,collections.abc.Hashable
是一个 typing.Protocol
.
typeshed/stdlib/_collections_abc.pyi:
from typing import (
AbstractSet as Set,
AsyncGenerator as AsyncGenerator,
AsyncIterable as AsyncIterable,
AsyncIterator as AsyncIterator,
Awaitable as Awaitable,
ByteString as ByteString,
Callable as Callable,
Collection as Collection,
Container as Container,
Coroutine as Coroutine,
Generator as Generator,
Generic,
Hashable as Hashable,
ItemsView as ItemsView,
Iterable as Iterable,
Iterator as Iterator,
KeysView as KeysView,
Mapping as Mapping,
MappingView as MappingView,
MutableMapping as MutableMapping,
MutableSequence as MutableSequence,
MutableSet as MutableSet,
Reversible as Reversible,
Sequence as Sequence,
Sized as Sized,
TypeVar,
ValuesView as ValuesView,
)
@runtime_checkable
class Hashable(Protocol, metaclass=ABCMeta):
# TODO: This is special, in that a subclass of a hashable class may not be hashable
# (for example, list vs. object). It's not obvious how to represent this. This class
# is currently mostly useless for static checking.
@abstractmethod
def __hash__(self) -> int: ...
为什么在 Mypy 下,__subclasshook__
适用于来自 collections.abc
的一招小马,但不适用于用户定义的 类?
比如这个程序
from collections.abc import Hashable
class A:
def __hash__(self) -> int:
return 0
a: Hashable = A()
产出
$ mypy demo.py --strict
Success: no issues found in 1 source file
但是这个等效程序
from abc import ABCMeta, abstractmethod
def _check_methods(C: type, *methods: str) -> bool:
mro = C.__mro__
for method in methods:
for B in mro:
if method in B.__dict__:
if B.__dict__[method] is None:
return NotImplemented
break
else:
return NotImplemented
return True
class Hashable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __hash__(self) -> int:
return 0
@classmethod
def __subclasshook__(cls, C: type) -> bool:
if cls is Hashable:
return _check_methods(C, "__hash__")
return NotImplemented
class A:
def __hash__(self) -> int:
return 0
a: Hashable = A()
产出
$ mypy demo.py --strict
demo.py:32: error: Incompatible types in assignment (expression has type "A", variable has type "Hashable")
Found 1 error in 1 file (checked 1 source file)
Mypy 是否以特殊方式处理一招小马?
是的,mypy
将这些 class 视为特殊情况。请记住,mypy
用于 static 类型检查,这意味着它无需 运行 您的代码即可工作,仅分析源代码。它实际上从未 调用 __subclasshook__
或类似的东西来确定什么是可散列的或不可散列的。您的“等效” class 仅在运行时等效,因为它依赖于 __subclasshook__
被调用。
如果你想 mypy
处理它不知道的东西,你必须写一个 mypy
plugin 来处理它。
Mypy 不使用标准库的实现,而是使用 typeshed 包中的规范(“存根文件”)。在这个包中,collections.abc.Hashable
是一个 typing.Protocol
.
typeshed/stdlib/_collections_abc.pyi:
from typing import (
AbstractSet as Set,
AsyncGenerator as AsyncGenerator,
AsyncIterable as AsyncIterable,
AsyncIterator as AsyncIterator,
Awaitable as Awaitable,
ByteString as ByteString,
Callable as Callable,
Collection as Collection,
Container as Container,
Coroutine as Coroutine,
Generator as Generator,
Generic,
Hashable as Hashable,
ItemsView as ItemsView,
Iterable as Iterable,
Iterator as Iterator,
KeysView as KeysView,
Mapping as Mapping,
MappingView as MappingView,
MutableMapping as MutableMapping,
MutableSequence as MutableSequence,
MutableSet as MutableSet,
Reversible as Reversible,
Sequence as Sequence,
Sized as Sized,
TypeVar,
ValuesView as ValuesView,
)
@runtime_checkable
class Hashable(Protocol, metaclass=ABCMeta):
# TODO: This is special, in that a subclass of a hashable class may not be hashable
# (for example, list vs. object). It's not obvious how to represent this. This class
# is currently mostly useless for static checking.
@abstractmethod
def __hash__(self) -> int: ...