Это не проблема Protocol
. Вы изменяете подпись от foo
до более строгих вариантов . Base.foo()
принимает любую Event
реализацию, в то время как каждый из ваших подклассов принимает только одну конкретную реализацию. Это нарушение принципа подстановки Лискова , и Mypy правильно, если не допустит этого.
Здесь вам придется использовать комбинацию связанных TypeVar
и Generic
, поэтому Вы можете создать различные конкретные подклассы 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
слотом шаблона, в который вы можете подключить что угодно, до тех пор, пока что-нибудь реализует ваш протокол. Это также намного надежнее, поскольку теперь вы не можете случайно смешать реализации Event
(объединяя threading.Event
и multiprocessing.Event
в одном подклассе).
Таким образом, следующие подклассы для двух разных * Реализации 1028 * верны:
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