【问题标题】:How can I create a Protocol that encompases both threading.Event and multiprocessing.Event?如何创建一个包含 threading.Event 和 multiprocessing.Event 的协议?
【发布时间】:2020-01-16 16:51:14
【问题描述】:

在 Python 标准库中,multiprocessing.Event 被明确声明为threading.Event 的克隆并具有相同的接口。我想注释变量和参数,以便它们可以接受这些类中的任何一个,mypy 会对它们进行类型检查。我尝试创建一个协议(我使用了multiprocessing.synchronize.Event,因为这是multiprocessing.Event 返回的实际类)。

import multiprocessing
import threading

from typing import Optional, Type, Protocol


class Event(Protocol):
    def wait(self, timeout: Optional[float]) -> bool:
        ...

    def set(self) -> None:
        ...

    def clear(self) -> None:
        ...

    def is_set(self) -> bool:
        ...


class Base:
    flag_class: Type[Event]

    def foo(self, e: Event):
        pass


class DerivedOne(Base):
    flag_class = multiprocessing.synchronize.Event

    def foo(self, e: multiprocessing.synchronize.Event):
        pass


class DerivedTwo(Base):
    flag_class = threading.Event

    def foo(self, e: threading.Event):
        pass

但是,mypy(版本 0.761)无法识别 multiprocessing.Eventthreading.Event 都实现了我定义的协议:

$ mypy proto.py

proto.py:31: error: Argument 1 of "foo" is incompatible with supertype "Base"; supertype defines the argument type as "Event"
proto.py:38: error: Argument 1 of "foo" is incompatible with supertype "Base"; supertype defines the argument type as "Event"
Found 2 errors in 1 file (checked 1 source file)

为什么mypy 无法识别我的协议,我该如何解决?

【问题讨论】:

    标签: python type-hinting mypy


    【解决方案1】:

    这不是Protocol 问题。您将foo 的签名更改为更严格的变体Base.foo() 接受 any Event 实现,而您的每个子类只接受一个具体实现。这违反了Liskov substitution principle,Mypy 不允许这样做是正确的。

    您必须在此处使用绑定的TypeVarGeneric 的组合,这样您就可以创建不同类型的Base 的具体子类:

    from typing import Generic, Optional, Protocol, Type, TypeVar
    
    # Only things that implement Event will do
    T = TypeVar("T", bound=Event)
    
    # Base is Generic, subclasses need to state what exact class
    # they use for T; as long as it's an Event implementation, that is.
    class Base(Generic[T]):
        flag_class: Type[T]
    
        def foo(self, e: T) -> None:
            pass
    

    这实质上使Base 成为一种模板类,T 是模板槽,您可以将任何东西插入其中,只要 anything 实现您的协议。这也更加健壮,因为您现在不会意外混淆 Event 实现(在单个子类中组合 threading.Eventmultiprocessing.Event)。

    所以以下两个不同Event 实现的子类是正确的:

    class DerivedOne(Base[multiprocessing.synchronize.Event]):
        flag_class = multiprocessing.synchronize.Event
    
        def foo(self, e: multiprocessing.synchronize.Event) -> None:
            pass
    
    class DerivedTwo(Base[threading.Event]):
        flag_class = threading.Event
    
        def foo(self, e: threading.Event) -> None:
            pass
    

    但是使用不实现协议方法的类是错误的:

    # Mypy flags the following class definition as an error, because a lock
    # does not implement the methods of an event.
    # error: Type argument "threading.Lock" of "Base" must be a subtype of "proto.Event"
    class Wrong(Base[threading.Lock]):
        flag_class = threading.Lock
    
        def foo(self, e: threading.Lock) -> None:
            pass
    

    混合类型也是错误的:

    # Mypy flags 'def foo' as an error because the type it accepts differs from
    # the declared type of the Base[...] subclass
    # error: Argument 1 of "foo" is incompatible with supertype "Base"; supertype defines the argument type as "Event"
    class AlsoWrong(Base[threading.Event]):
        flag_class = threading.Event
    
        def foo(self, e: multiprocessing.synchronize.Event) -> None:
            pass
    
    # Mypy flags 'flag_class' as an error because the type differs from the
    # declared type of the Base[...] subclass
    # error: Incompatible types in assignment (expression has type "Type[multiprocessing.synchronize.Event]", base class "Base" defined the type as "Type[threading.Event]")
    class StillWrong(Base[threading.Event]):
        flag_class = multiprocessing.synchronize.Event
    
        def foo(self, e: threading.Event) -> None:
            pass
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多