Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 472
squirrel Avatar asked Apr 27 '20 20:04

squirrel


People also ask

How do you declare a generic interface?

Generic class parameters are specified in angle brackets “<>” after the class name as of the instance variable. Generic constructors are the same as generic methods. For generic constructors after the public keyword and before the class name the type parameter must be placed.

How do you write a generic method in Python?

Generic functions: from typing import TypeVar, Sequence T = TypeVar('T') # Declare type variable def first(seq: Sequence[T]) -> T: return seq[0] def last(seq: Sequence[T]) -> T: return seq[-1] n = first([1, 2, 3]) # n has type int.

What is a generic interface?

A generic interface is primarily a normal interface like any other. It can be used to declare a variable but assigned the appropriate class. It can be returned from a method. It can be passed as argument. You pass a generic interface primarily the same way you would an interface.


1 Answers

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.

like image 184
user2357112 supports Monica Avatar answered Sep 28 '22 00:09

user2357112 supports Monica