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

How to get Mypy working with multiple mixins relying on each other?

目前在 Electrum 中,我们使用 Union type on self 来访问来自多个混入父 class 的方法。例如,QtPluginBase 依赖于混入 HW_PluginBase 的子 class 来工作。例如,有效用法是 class TrezorPlugin(QtPluginBase, HW_PluginBase).

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

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

考虑 Trezor + Qt:

对于 Qt,我们有这个 class 层次结构:

对于 Trezor,我们有:

并创建实际的 Qt Trezor 插件:

重点是基本的 gui-neutral 插件将首先获得制造商特定的方法;然后它将获得特定于 gui 的方法。

Aaron(在评论中)建议 QtPluginBase 可以替代 class HW_PluginBase,但这意味着制造商特定的东西会出现在后面,这意味着结果 classes 不能被 CLI 或 Kivy 使用。

注意两者

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

electrum.plugins.hw_wallet.qt.QtPluginBase

HW_PluginBase。他们不能都 subclass 它。

所以如果我们避免混入,那么唯一的选择就是 QtPluginBase subclass TrezorPlugin(但有很多制造商),或者 TrezorPlugin 可以 subclass QtPluginBase 但是,再一次,结果 classes 不能被 CLI 或 Kivy 使用。

我意识到 Union 是一个 "or",所以提示确实没有意义。但是没有 Intersection 类型。使用 Union,大多数 PyCharm 功能都可以使用。

如果 QtPluginBase 可以有一个类型提示它子 class 是 HW_PluginBase,但实际上没有子class运行时间。

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

mypy doesn't offer an Intersection type yet 以来,您无法正确键入 self arg(并且 Union 不能替代它!)。您可以做的是为 mixin 引入基 类 仅用于类型检查。这是我在 Django 项目中使用 mixin 时经常使用的技巧。示例:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .plugin import HW_PluginBase
    _Base = HW_PluginBase
else:
    _Base = object


class QtPluginBase(_Base):
    def load_wallet(self, wallet: 'Abstract_Wallet', window: ElectrumWindow):
        ...

您现在可以放弃 self 的显式输入,因为 mypy 可以推断出所有必要的基础 类 本身。

随着PEP-544(Python3.8+)新增的Protocols,可以自己定义交集接口了!这也让您可以在 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

感谢 SomberNight/ghost43 此文件的初始版本。