如何让 Mypy 与多个相互依赖的 mixins 一起工作?

Jan*_*sen 7 python multiple-inheritance generic-programming python-3.x mypy

目前,在 Electrum 中,我们使用Unionon 类型self来访问来自多个混合父类的方法。例如,QtPluginBase依靠混合到 的子类中来HW_PluginBase工作。例如,有效的用途是class TrezorPlugin(QtPluginBase, HW_PluginBase).

有 Qt gui、Kivy gui,还有 CLI。尽管 Kivy 尚未实现硬件钱包,但未来可能会实现。您已经可以在 CLI 上使用它们。

然而,也有多家硬件钱包制造商,都有自己的插件。

考虑 Trezor + Qt:

对于 Qt,我们有这样的类层次结构:

  • electrum.plugins.hw_wallet.qt.QtPluginBase被使用过
  • electrum.plugins.trezor.qt.QtPlugin(QtPluginBase)

对于 Trezor,我们有:

  • electrum.plugin.BasePlugin被使用过
  • electrum.plugins.hw_wallet.plugin.HW_PluginBase(BasePlugin)被使用过
  • electrum.plugins.trezor.trezor.TrezorPlugin(HW_PluginBase)

创建实际的 Qt Trezor 插件:

  • electrum.plugins.trezor.qt.Plugin(TrezorPlugin, QtPlugin)

关键是基本的 gui 中立插件将首先获得制造商特定的方法;然后它将获得 GUI 特定的方法。

Aaron(在评论中)建议QtPluginBase可以 subclass HW_PluginBase,但这意味着制造商特定的内容将出现在后面,这意味着生成的类不能被 CLI 或 Kivy 使用。

请注意,两者

electrum.plugins.trezor.trezor.TrezorPlugin(HW_PluginBase)

electrum.plugins.hw_wallet.qt.QtPluginBase

依靠HW_PluginBase。他们不能都将其子类化。

因此,如果我们避免混合,那么唯一的选择就是要么有QtPluginBase子类TrezorPlugin(但有很多制造商),要么TrezorPlugin可以子类化QtPluginBase,但同样,生成的类不能被 CLI 或 Kivy 使用。

我意识到这Union是一个“或”,所以这个提示确实没有意义。但没有Intersection类型。通过 Union,大多数 PyCharm 功能都可以工作。

一件好事是,如果QtPluginBase可以有一个类型提示,表明它是子类化的HW_PluginBase,但在运行时实际上没有子类化。

如何使用 Mypy 键入此内容,而不必Union在每个方法上使用这种 hacky 类型提示(因为每个方法都有self)?

Jan*_*sen 5

通过PEP-544 (Python 3.8+)中添加的协议,您可以自己定义交集接口!这还可以让您隐藏ClassA您不想ClassB使用的实现细节。

from typing import Protocol

class InterfaceAB(Protocol):
    def method_a(self) -> None: ...
    def method_b(self) -> None: ...

class ClassA:
    def method_a(self) -> None:
        print("a")

class ClassB:
    def method_b(self: InterfaceAB) -> None:
        print("b")
        self.method_a()

# if I remove ClassA here, I get a type checking error!
class AB(ClassA, ClassB): pass

ab = AB()
ab.method_b()

# % mypy --version
# mypy 0.761
# % mypy mypy-protocol-demo.py
# Success: no issues found in 1 source file
Run Code Online (Sandbox Code Playgroud)

此文件的初始版本归功于 SomberNight/ghost43。