如何与mypy一起使用__subclasshook__?

发布于 2025-01-20 06:10:32 字数 1506 浏览 1 评论 0原文

为什么在 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)

Does Mypy handle one-trick pony in aspecial way?

How come that under Mypy, __subclasshook__ works for one-trick ponies from collections.abc, but not for user-defined classes?

For instance, this program

from collections.abc import Hashable

class A:
    def __hash__(self) -> int:
        return 0

a: Hashable = A()

outputs

$ mypy demo.py --strict
Success: no issues found in 1 source file

But this equivalent program

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()

outputs

$ 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)

Does Mypy handle one-trick ponies in a special way?

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

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

发布评论

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

评论(2

空宴 2025-01-27 06:10:32

mypy不使用标准库的实现,而是使用 typeshed collections.abc.hashable 是typing.protocol

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

typeshed/stdlib/stdlib/typ.pyi.pyi.pyi :pyi :

@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 does not use the implementations of the standard library but its specifications (’stub files’) from the typeshed package. In this package, collections.abc.Hashable is a 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,
)

typeshed/stdlib/typing.pyi:

@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: ...
染墨丶若流云 2025-01-27 06:10:32

是的,mypy将这些类别视为特殊情况。请记住,mypy适用于 static 类型检查,这意味着它根本不运行代码,只能分析源代码。它从来没有真正呼叫 __ subclasshook __或可以确定什么是不可悬浮的内容。您的“等效”类仅在运行时等效,因为它依赖于__ subclasshook __被调用。

如果您想要mypy与它尚不了解的东西一起工作,则必须编写 mypy插件处理它。

Yes, mypy treats these kind of classes as special cases. Remember, mypy is for static type checking, which means it works without running your code at all, only analyzing the source code. It never actually calls __subclasshook__ or the like to determine what is or isn't hashable. Your "equivalent" class is only equivalent at runtime, as it relies on __subclasshook__ being called.

If you want mypy to work with something it doesn't already know about, you would have to write a mypy plugin to handle it.

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