【问题标题】:How to get Mypy working with multiple mixins relying on each other?如何让 Mypy 使用多个相互依赖的 mixin?
【发布时间】:2020-04-06 06:43:39
【问题描述】:

目前在 Electrum 中,我们使用 Union type on 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-neutral插件将首先获得制造商特定的方法;那么它将获得特定于 gui 的方法。

Aaron(在 cmets 中)建议 QtPluginBase 可以继承 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 输入,而不必在每个方法上使用这个 hacky Union 类型提示(因为每个方法都有 self)?

【问题讨论】:

  • 我会尝试深入挖掘,但我的第一个方法是创建自定义类型别名以及基类:mypy.readthedocs.io/en/latest/kinds_of_types.html#type-aliases
  • @JanusTroelsen 我试图用你的“Union type on self”创建一个 MCVE,但我得到了error: Item "QtPluginBase" of "Union[QtPluginBase, HW_PluginBase]" has no attribute "keystore_class"
  • @aaron 这是来自 Mypy 的错误吗?我看到keystore_classdefined in HW_PluginBase,不知道为什么Mypy不识别?
  • @JanusTroelsen 这似乎是正确的。 Union 表示 orQtPluginBase 没有 keystore_class。如果它的方法直接依赖于它,为什么它不继承 HW_PluginBase

标签: python python-3.x multiple-inheritance generic-programming mypy


【解决方案1】:

使用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

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

【讨论】:

  • 我比我自己的答案更喜欢这个!提示:对于 pythontyping_extensions 中有一个协议的反向端口,例如if sys.version_info >= (3, 8): from typing import Protocol; else: from typing_extensions import Protocol.
【解决方案2】:

由于mypy doesn't offer an Intersection type yet,您无法正确输入self 参数(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 可以自行推断所有必要的基类。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-06
    相关资源
    最近更新 更多