squirrel
squirrel

Reputation: 5488

How do I create a generic interface in Python?

I want to create the equivalent of this in 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;
    }
}

This is my attempt (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

...that fails with main.py:11: error: Invariant type variable 'E' used in protocol where contravariant one is expected. Unless I'm misunderstanding, Java seems to be fine with an invariant one, and this is my idea as well; I don't want various Filters to be compatible with one another. In any case, slapping contravariant=True onto T doesn't work either. So,

Why the protocol needs a contravariant variable? And, how do I make this Python code type check?

Upvotes: 10

Views: 13009

Answers (1)

user2357112
user2357112

Reputation: 280973

Protocols don't allow that, because it breaks subtype transitivity. See PEP 544.

If you have the following two classes:

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

class B(A):
    def method(self, arg: object):
        pass

then B is a valid subclass of A, because B.method can accept any arguments A.method can. However, if you could introduce the following protocol:

T = typing.TypeVar('T')

class Proto(typing.Protocol[T]):
    def method(self, arg: T):
        pass

then A would satisfy Proto[int], but B would not, due to the invariance of T.

Upvotes: 10

Related Questions