如何让 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 层次结构:
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(在评论中)建议 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 此文件的初始版本。
目前在 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 层次结构:
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(在评论中)建议 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 此文件的初始版本。