我有两个基类,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
嘎嘎叫,那么{obj: Foo
。到现在为止,一直都还不错。在
现在有另一个类FooBar
,它子类Bar
,行为类似Foo
,但它不能子类Foo
,因为它通过属性公开其属性(因此__init__
参数没有意义):
此时,执行Worker(FooBar())
显然会导致类型检查器错误:
为了将Foo
-ish的接口与类型检查器通信,我考虑为Foo
-ish类型创建一个抽象基类:
import abc
class Fooish(abc.ABC):
x : int
@abc.abstractmethod
def foo(self) -> int:
raise NotImplementedError
但是我不能让FooBar
从Fooish
继承,因为Bar
有自己的元类,因此这会导致元类冲突。所以我想在Foo
和{Fooish.register
,但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"
我考虑的下一个选项是创建一个接口,而不以“normal”类的形式从abc.ABC
继承,然后让Foo
和{
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
(abstract)基类现在是可实例化的,并且是Worker(...)
的有效参数,尽管它没有提供属性x
,所以没有意义。在
现在我被困在如何在不使用继承的情况下将这个接口与类型检查器通信(由于元类冲突;即使有可能,mypy仍然会抱怨x
的签名不兼容)。有办法吗?在
从python3.8开始,PEP 544 Protocols: Structural subtyping (static duck typing)添加了对structural subtyping的支持。对于3.8之前的版本,相应的实现由PyPI上的typing-extensions包提供。在
与所讨论的场景相关的是^{} ,正如PEP in more detail所解释的那样。这允许定义implicit subtypes,因为不需要继承,所以可以避免元类冲突问题。代码如下:
error: Signature of "x" incompatible with supertype "Fooish"
,可以对x: typing.Any
进行注释。在Fooish
真正抽象,需要一些技巧来解决元类冲突。我从this answer取了一份食谱:之后,可以创建
^{pr2}$Fooish
:在运行时成功执行且未显示mypy错误的整个代码:
现在是时候考虑你真的想把},它不调用
Fooish
抽象化,因为如果MyMeta
做很多技巧,做class Fooish(metaclass=MyABCMeta):
会有副作用。例如,如果MyMeta
定义__new__
,那么您可能可以在Fooish
中定义{MyMeta.__new__
,而是调用abc.ABCMeta.__new__
。但事情会变得复杂。。。所以,也许有非抽象的Fooish
会更容易。在如果我理解,您可以添加一个
Union
,它基本上允许Foo or Bar or Fooish
:包括以下内容:
^{pr2}$参见:
相关问题 更多 >
编程相关推荐