Python中实现无继承的接口

Mic*_*eno 5 python python-typing pylance

我有一个排序的链表

class SortedLinkedList:
    # ...
    def insert(self, value: int):
        # ...
        if node.value > value:
            self.add_before(node, value)
        # ...
Run Code Online (Sandbox Code Playgroud)

我想将 a 可以保存的值类型概括为Node从仅s 到通过实现魔术方法int重载运算符的任何对象。>__gt__()

在其他语言中,我会通过使用 来实现这一点Interface,但 Python 显然没有类似的东西。我已经看到通过使用抽象类来伪造接口的建议,例如

class Sortable(ABC):
    @abstractmethod
    def __gt__(self, other) -> bool:
        pass

class SortedLinkedList:
    # ...
    def insert(self, value: Sortable, node: Node):
        # ...
Run Code Online (Sandbox Code Playgroud)

问题是这种方法需要扩展和使用 的子类,这意味着不能使用Sortable已经具有整数等功能的类型>

linkedlist.insert(5) # Pylance red squiggles
Run Code Online (Sandbox Code Playgroud)
Argument of type "Literal[5]" cannot be assigned to
parameter "value" of type "Sortable" in function "insert"
  "Literal[5]" is incompatible with "Sortable" Pylance(reportGeneralTypeIssues) 
Run Code Online (Sandbox Code Playgroud)

我知道,鉴于 Python 的动态鸭子类型和隐式样式,接口在运行前不是必需的。我不是粉丝,并且选择使用可用的工具(如打字Pylance)来实现严格类型化的开发人员体验。

我也不打算使用像.hasattr(value, '__gt__'). 我希望它在类型系统/语言服务器/IDE 级别上注册,因为表达性、可读性和 IDE 智能感知是严格类型的主要好处。

有什么办法可以实现这一点吗?

pyd*_*ner 7

您要找的是typing.Protocol.

class Sortable(Protocol):
    def __gt__(self, other) -> bool: ...
Run Code Online (Sandbox Code Playgroud)

这样,任何定义的类都__gt__将被检测为 的隐式子类型Sortable。请注意,在 Python >= 3.8 中,您可能需要进行other定位:

class Sortable(Protocol):
    def __gt__(self, other, /) -> bool: ...
Run Code Online (Sandbox Code Playgroud)

__gt__这是因为类型检查器可能不会像处理普通方法那样处理 dunder 方法,因此期望参数可能需要通过关键字调用。添加/告诉检查器该__gt__参数永远不需要通过关键字传递。相反,如果参数仅通过关键字传递,请相应地标记协议的方法:

class Something(Protocol):
    def method(self, *, name) -> str: ...
Run Code Online (Sandbox Code Playgroud)