我有一个显示各种网格的应用程序。网格有不同种类的功能,所以我的设计是一个基本网格类,处理一般的网格事物,和各种功能混合到一个类与基本网格:
class BaseGrid(wx.grid.Grid):
def foo():
return 0
class Grid_Mixin1():
def feature():
self.foo()
class Grid_Mixin2():
def feature():
self.foo()
class SpecificGrid(Mixin1, BaseGrid):
...
问题是我试图使用类型提示,并且在mixins中,类型检查器不知道self.foo()
将存在,引发未知成员错误。
我决定使用协议来让静态类型检查器知道混合符合什么:
from typing import Protocol
class MyProtocol(Protocol):
def foo(self):
...
class Grid_Mixin1(MyProtocol):
def feature():
self.foo()
class Grid_Mixin2(MyProtocol):
def feature():
self.foo()
现在当我尝试使用SpecificGrid
时,我得到了臭名昭著的
TypeError:元类冲突:派生类的元类必须是其所有基类的元类的(非严格)子类
我只能假设wx.grid.Grid是从它自己的元类派生的?从文档中我看不出来,但这是我唯一的解释。我的评估正确吗?我该怎么做才能避开这个问题呢?
是的-它们有不同的元类,因此您必须创建一个组合元类才能组合这两个类。
这比听起来容易——因为wx和typing都是维护良好的代码,并且在类布局中没有固有的冲突,因此创建一个无冲突的元类只需要组合两个元类。
然而,问题在于打字。协议子类:这个层次结构并不是要指定真正的具体类——它是要指定一个接口,用来描述其他可能代表也可能不代表它的类。(这与collections.abc
中的类不同-它描述了一个"协议"和也有mixin方法的实现)
这意味着当一个组合Protocol和wx.grid的元类时。网格,得到的是另一个错误:
In [2]: import wx.grid
In [3]: wx.grid.Grid.__class__
Out[3]: sip.wrappertype
In [4]: m1 = wx.grid.Grid.__class__
In [5]: from typing import Protocol
In [6]: m2 = Protocol.__class__
In [7]: class m3(m1, m2): pass
In [8]: class test(wx.grid.Grid, Protocol): pass
[...]
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
In [9]: class test(wx.grid.Grid, Protocol, metaclass=m3): pass
[...]
TypeError: Protocols can only inherit from other protocols, got <class 'wx._grid.Grid'>
所以,正确的做法是根本不继承协议——实现你的类,实现你的协议的具体实现,就像一个mixin,并使用它来组成你的网格类——并拥有你的协议层次结构就像一组虚拟类,只包含方法、属性和它们的注释——这将继承自typing.Protocol
如果你不想写两次方法和注释的声明,这是可以理解的,可以声明实现协议的具体类,并使用一些代码在继承Protocol
的类的体中运行。这段代码可以将方法(及其注释)从另一个类复制到您的协议(抽象)类中——然后它将与静态检查器和其他依赖于静态注释的软件一起工作:
In [19]: class MyBase:
...: def foo(self) -> None: pass
...:
In [20]: class MyProtcol(Protocol):
...: for name in dir(MyBase):
...: if not name.startswith("__"):
...: locals()[name] = getattr(PBase, name)
...:
...:
In [21]: MyProtcol._is_protocol
Out[21]: True