Currently in Electrum we use the Union
type on self
to be able to access methods from multiple mixed-in parent classes. For example, QtPluginBase
relies on being mixed into a subclass of HW_PluginBase
to work. For example, a valid use is class TrezorPlugin(QtPluginBase, HW_PluginBase)
.
There is the Qt gui, the Kivy gui, and there is also CLI. Although hardware wallets are not implemented for Kivy, they could be in the future. You can already use them on the CLI.
However there are also multiple hardware wallet manufacturers, all with their own plugins.
Consider Trezor + Qt:
For Qt, we have this class hierarchy:
electrum.plugins.hw_wallet.qt.QtPluginBase
used byelectrum.plugins.trezor.qt.QtPlugin(QtPluginBase)
For Trezor, we have:
electrum.plugin.BasePlugin
used byelectrum.plugins.hw_wallet.plugin.HW_PluginBase(BasePlugin)
used byelectrum.plugins.trezor.trezor.TrezorPlugin(HW_PluginBase)
And to create the actual Qt Trezor plugin:
electrum.plugins.trezor.qt.Plugin(TrezorPlugin, QtPlugin)
The point is that the base gui-neutral plugin will first gain manufacturer-specific methods; then it will gain gui-specific methods.
Aaron (in the comments) suggests that QtPluginBase
could subclass HW_PluginBase
, but that would mean that the manufacturer-specific stuff would come after, which means the resulting classes cannot be used by the CLI or Kivy.
Note that both
electrum.plugins.trezor.trezor.TrezorPlugin(HW_PluginBase)
and
electrum.plugins.hw_wallet.qt.QtPluginBase
rely on HW_PluginBase
. They can't both subclass it.
So if we avoid mix-ins, then the only alternative would be to either have QtPluginBase
subclass TrezorPlugin
(but there are many manufacturers), or TrezorPlugin
could subclass QtPluginBase
but then, again, the resulting classes cannot be used by the CLI or Kivy.
I realize that Union
is an "or", so the hint is indeed not making sense. But there is no Intersection
type. With Union, most of the PyCharm functionality works.
One thing that would be nice is if QtPluginBase
could have a type-hint that it subclasses HW_PluginBase
, but without actually subclassing at runtime.
How could this be typed with Mypy without having to use this hacky Union
type hint on every method (since every method has self
)?
With the Protocols added in PEP-544 (Python 3.8+), you can define the intersection interface yourself! This also lets you hide implementation details in ClassA
that you don't want ClassB
to use.
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
Credits to SomberNight/ghost43 for the initial version of this file.