是否可以继承 wx.grid.Grid 并使用我自己的元类?

Is it possible to subclass wx.grid.Grid and also use my own metaclass?

我有一个显示各种网格的应用程序。网格具有不同种类的功能,所以我的设计是一个处理通用网格事物的基本网格 class,以及混合到具有基本网格的 class 中的各种功能混合:

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):
    ...

问题是我正在尝试使用类型提示,在 mixin 内部,类型检查器不知道 self.foo() 会存在,从而引发未知成员错误。

我决定使用协议让静态类型检查器知道 mixins 符合什么:

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: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

我只能假设 wx.grid.Grid 是从它自己的元 class 派生出来的?从文档中看,这对我来说并不明显,但这是我唯一的解释。我的评估正确吗?我该怎么做才能解决这个问题?

是的 - 它们有不同的元class,因此您必须创建一个组合的元class才能将两个class结合起来。

这比听起来容易 - 因为 wx 和 typing 都是维护良好的代码,并且在 class 布局中没有内在冲突,创建一个不冲突的 metaclass 只是一个问题结合 metaclasses.

然而,问题在于 typing.Protocol subclasses:这个层次结构并不意味着指定真正的具体 classes - 它意味着指定一个描述其他的接口classes 可能代表也可能不代表它。 (这与 collections.abc 中的 classes 不同——它描述了 'protocol' 和 还有 的混合方法实现)

这意味着当一个 将协议的元 class 和 wx.grid.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'>

因此,正确的做法是根本不继承协议 - 实现具有协议具体实现的 class,就像一个混入,并正常使用它来组成你的网格class - 还有你的 协议层次结构就像一组虚拟 classes,仅包含方法、属性及其注释 - 这将继承自 typing.Protocol

如果你不想写两次方法和注解的声明,这是可以理解的,可以声明实现协议的具体classes,并使用一些代码来运行 在继承自 Protocol 的 class 的 body 中。此代码可以将方法(及其注释)从其他 class 复制到您的协议(抽象)class - 然后它将与静态检查器和依赖静态注释的其他软件一起工作:

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