У меня есть два базовых класса, Foo
и Bar
, и класс Worker
, который ожидает объекты, которые ведут себя как Foo
. Затем я добавляю еще один класс, который реализует все соответствующие атрибуты и методы из Foo
, но мне не удалось передать это успешно статической проверке типов через mypy. Вот небольшой пример:
class MyMeta(type):
pass
class Bar(metaclass=MyMeta):
def bar(self):
pass
class Foo:
def __init__(self, x: int):
self.x = x
def foo(self):
pass
class Worker:
def __init__(self, obj: Foo):
self.x = obj.x
Здесь Worker
фактически принимает любой Foo
-иш-объект, то есть объекты, имеющие атрибут x
и метод foo
. Так что если obj
ходит как Foo
и если он крякает как Foo
, тогда Worker
будет счастлив. Теперь весь проект использует подсказки типов, и на данный момент я указываю obj: Foo
. Пока все хорошо.
Теперь есть еще один класс FooBar
, который подклассы Bar
и ведет себя как Foo
, но он не может подкласс Foo
, потому что он выставляет свои атрибуты через свойства (и поэтому__init__
параметры не имеют смысла):
class FooBar(Bar):
"""Objects of this type are bar and they are foo-ish."""
@property
def x(self) -> int:
return 0
def foo(self):
pass
На этом этапе выполнение Worker(FooBar())
, очевидно, приводит к ошибке проверки типов:
error: Argument 1 to "Worker" has incompatible type "FooBar"; expected "Foo"
Использование абстрактного базового класса
Чтобы передать интерфейс Foo
-ish в средство проверки типов, я подумал о создании абстрактного базового класса для Foo
-ish типов:
import abc
class Fooish(abc.ABC):
x : int
@abc.abstractmethod
def foo(self) -> int:
raise NotImplementedError
Однако я не могуFooBar
не наследуется от Fooish
, потому что Bar
имеет свой собственный метакласс, и это может вызвать конфликт метаклассов. Поэтому я подумал об использовании Fooish.register
на Foo
и FooBar
, но mypy не согласен:
@Fooish.register
class Foo:
...
@Fooish.register
class FooBar(Bar):
...
class Worker:
def __init__(self, obj: Fooish):
self.x = obj.x
, что приводит к следующим ошибкам:
error: Argument 1 to "Worker" has incompatible type "Foo"; expected "Fooish"
error: Argument 1 to "Worker" has incompatible type "FooBar"; expected "Fooish"
Использование«нормальный» класс как интерфейс
Следующий вариант, который я рассмотрел, - это создание интерфейса без наследования от abc.ABC
в форме «нормального» класса, а затем наследование от него и 1054 *, и FooBar
:
class Fooish:
x : int
def foo(self) -> int:
raise NotImplementedError
class Foo(Fooish):
...
class FooBar(Bar, Fooish):
...
class Worker:
def __init__(self, obj: Fooish):
self.x = obj.x
Теперь mypy не жалуется на тип аргумента Worker.__init__
, но жалуется на несовместимость сигнатур FooBar.x
(то есть property
) с Fooish.x
:
error: Signature of "x" incompatible with supertype "Fooish"
Также базовый класс Fooish
(абстрактный) теперь является инстанцируемым и допустимым аргументом для Worker(...)
, хотя это не имеет смысла, так как не предоставляет атрибут x
.
Вопрос ...
Теперь я застрял в вопросе о том, как передать этот интерфейс в средство проверки типов без использования наследования (из-за конфликта метаклассов; даже если бы это было возможно, mypy все равно жаловалась быо несовместимости подписи x
). Есть ли способ сделать это?