我有两个基本类,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
-ish对象,即具有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的接口(interface)传达给类型检查器,我考虑过为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"
使用“普通”类作为接口(interface)
我考虑的下一个选项是创建一个接口(interface),而不以“普通”类的形式从
abc.ABC
继承,然后让Foo
和FooBar
都从该接口(interface)继承: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
属性,因此它是没有意义的。问题...
现在,我陷入了一个问题,即如何在不使用继承的情况下将该接口(interface)与类型检查器进行通信(由于元类冲突;即使有可能,mypy仍会抱怨
x
的签名不兼容)。有办法吗? 最佳答案
error: Signature of "x" incompatible with supertype "Fooish"
,可以注释x: typing.Any
。 Fooish
真正抽象,需要一些技巧来解决元类冲突。我从this answer拿了一个食谱:class MyABCMeta(MyMeta, abc.ABCMeta):
pass
之后,可以创建
Fooish
:class Fooish(metaclass=MyABCMeta):
整个代码在运行时成功执行,并且没有显示出来自mypy的错误:
import abc
import typing
class MyMeta(type):
pass
class MyABCMeta(abc.ABCMeta, MyMeta):
pass
class Fooish(metaclass=MyABCMeta):
x : typing.Any
@abc.abstractmethod
def foo(self) -> int:
raise NotImplementedError
class Bar(metaclass=MyMeta):
def bar(self):
pass
class Foo(Fooish):
def __init__(self, x: int):
self.x = x
def foo(self):
pass
class Worker:
def __init__(self, obj: Fooish):
self.x = obj.x
class FooBar(Bar, Fooish):
"""Objects of this type are bar and they are foo-ish."""
@property
def x(self) -> int:
return 0
def foo(self):
pass
print(Worker(FooBar()))
现在是时候考虑您是否真的要抽象
Fooish
了,因为如果class Fooish(metaclass=MyABCMeta):
有很多技巧,那么进行MyMeta
可能会有副作用。例如,如果MyMeta
定义了__new__
,则可能可以在__new__
中定义Fooish
,它不调用MyMeta.__new__
而是调用abc.ABCMeta.__new__
。但是事情可能变得复杂起来……因此,拥有非抽象的Fooish
可能会更容易。