У меня есть два базовых класса, 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
в форме "нормального" класса, а затем от него наследовать как Foo
, так и 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
). Есть способ сделать это?