如何在 Python 中创建通用接口?

squ*_*rel 5 python python-typing

我想在 Python 中创建等价物:

static class Event {}

static class MyEvent extends Event {}

interface Filter<E extends Event> {
    boolean filter(E event);
}

static class MyFilter implements Filter<MyEvent> {
    @Override public boolean filter(MyEvent event) {
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的尝试(mypy-play):

from typing import TypeVar, Protocol

class Event:
    pass

class MyEvent(Event):
    pass

E = TypeVar("E", bound=Event)

class Filter(Protocol[E]):
    def filter(self, event: E) -> bool:
        raise NotImplementedError

class MyFilter(Filter):
    def filter(self, event: MyEvent) -> bool:       # should be ok
        raise NotImplementedError

class BadFilter(Filter):
    def filter(self, event: object) -> bool:        # should fail
        raise NotImplementedError
Run Code Online (Sandbox Code Playgroud)

...失败了main.py:11: error: Invariant type variable 'E' used in protocol where contravariant one is expected。除非我误解了,Java 似乎对一个不变的没问题,这也是我的想法;我不希望各种Filters 相互兼容。在任何情况下,拍打contravariant=True到T 也不管用。所以,

为什么协议需要逆变变量?而且,如何进行此 Python 代码类型检查?

use*_*ica 5

协议不允许这样做,因为它破坏了子类型的传递性。参见PEP 544。

如果您有以下两个类:

class A:
    def method(self, arg: int):
        pass

class B(A):
    def method(self, arg: object):
        pass
Run Code Online (Sandbox Code Playgroud)

thenB是 的有效子类A,因为B.method可以接受任何参数A.methodcan。但是,如果您可以引入以下协议:

T = typing.TypeVar('T')

class Proto(typing.Protocol[T]):
    def method(self, arg: T):
        pass
Run Code Online (Sandbox Code Playgroud)

thenA会满足Proto[int],但B不会,因为 的不变性T。