Я хочу создать эквивалент этого в 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;
    }
}

Это моя попытка (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

... это не удается с main.py:11: error: Invariant type variable 'E' used in protocol where contravariant one is expected. Если я не ошибаюсь, Java, кажется, подходит для инвариантного, и это тоже моя идея; Я не хочу, чтобы различные Filter были совместимы друг с другом. В любом случае, наложение contravariant=True на T не работать либо. Так,

Зачем протоколу нужна контравариантная переменная? И как мне сделать эту проверку типа кода Python?

7
squirrel 27 Апр 2020 в 23:13
1
Ваша ссылка «тоже не работает» говорит «Успех: в 1 исходном файле проблем не обнаружено», когда я нажимаю «Выполнить».
 – 
user2357112
27 Апр 2020 в 23:17
1
О, подождите, вы ожидали, что BadFilter потерпит неудачу. Контравариантность означает, что BadFilter в порядке. Фильтр, который может фильтровать произвольные объекты, может фильтровать и события.
 – 
user2357112
27 Апр 2020 в 23:20
MyFilter фильтрует подклассы, а BadFilter фильтрует суперклассы. наверняка один из них должен выйти из строя?
 – 
squirrel
27 Апр 2020 в 23:24

1 ответ

Протоколы этого не допускают, потому что это нарушает транзитивность подтипа. См. PEP 544.

Если у вас есть следующие два класса:

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

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

Тогда B является допустимым подклассом A, потому что B.method может принимать любые аргументы, которые могут A.method. Однако, если бы вы могли ввести следующий протокол:

T = typing.TypeVar('T')

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

Тогда A удовлетворяло бы Proto[int], но B не удовлетворяло бы из-за инвариантности T.

7
user2357112 27 Апр 2020 в 23:26
Я предполагаю, что если бы я следовал моей идее Java, то есть если бы A явно реализовывал протокол, создание подкласса было бы ошибкой, и эта проблема не возникла бы. Думаю, я хочу либо использовать что-то другое, чем Protocol, либо вместо этого использовать контравариант TypeVar. Это моя последняя последняя попытка — хорошо выглядит?
 – 
squirrel
28 Апр 2020 в 00:17
@squirrel: На первый взгляд все в порядке. foo: Filter = BadFilter2() проходит, потому что Foo интерпретируется как Foo[Any], а Any в основном относится к типу "не проверять меня". Это похоже на использование необработанных типов в Java.
 – 
user2357112
28 Апр 2020 в 00:23
Ага, спасибо. есть идеи, почему это интерпретируется как Filter[Any] вместо Filter[E], как было определено? Я не уверен, что это работает в Java, поскольку Java требует явного объявления интерфейсов и просто не позволит мне сделать что-то вроде BadFilter2
 – 
squirrel
28 Апр 2020 в 00:40