有办法在Python中为多个接口提供类型提示吗?

1 投票
1 回答
55 浏览
提问于 2025-04-13 19:23

我一直在用Python的抽象基类(ABCs)来创建接口,并且有一个类实现了多个接口,像这样:

from abc import ABC, abstractmethod
from typing import override

class InterfaceOne(ABC):
    
    @abstractmethod
    def foo(self) -> None: ...

class InterfaceTwo(ABC):

    @abstractmethod
    def bar(self) -> None: ...

class Baz(InterfaceOne, InterfaceTwo):

    @override
    def foo(self) -> None: ...

    @override
    def bar(self) -> None: ...

我想给一个函数加上类型提示,这个函数需要操作那些必须同时实现这两个接口的类。我考虑过下面这种写法,但看起来语法不太对。

def do_something(obj: InterfaceOne & InterfaceTwo):
    obj.foo()
    obj.bar()

有没有人知道怎么做到这一点?根据我对文档的阅读,我只看到关于联合类型的提及,比如InterfaceOne | InterfaceTwo,这表示这个函数可以接受任意一个接口,但我希望这个函数只接受同时实现两个接口的类。

1 个回答

3

与其使用抽象基类,我发现使用协议要方便得多。协议可以看作是一种纯粹的抽象基类,它只定义了属性和方法的签名,换句话说,就是数据的形状。

在这个例子中,你可以这样定义类:

from typing import Protocol


class InterfaceOne(Protocol):
    def foo(self) -> None: ...


class InterfaceTwo(Protocol):
    def bar(self) -> None: ...


class Baz:
    def foo(self) -> None: ...

    def bar(self) -> None: ...

你会注意到,Baz 本身并没有提供一个父类。这没问题。在任何需要 InterfaceOneInterfaceTwo 的地方,你的类型检查工具只需要检查提供的类是否实现了所有声明的方法和属性……而不需要强制执行任何类的层级关系!

在我看来,这让这些数据类型更容易使用,也更能适应更多的使用场景,而不是强迫所有输入的定义都基于一个根父类。

当你想创建一个同时实现 InterfaceOneInterfaceTwo 的类时,你可以创建一个新的协议来强制这个约束。

class DoSomethingArg(Protocol, InterfaceOne, InterfaceTwo):
    pass


def do_something(obj: DoSomethingArg):
    obj.foo()
    obj.bar()

do_something(Baz())

再次注意,我们根本不需要修改 Baz,就能实现接口 InterfaceOne & InterfaceTwo 的交集。

撰写回答