Python类型提示:类型的交集(实现接口的类)

sam*_*ser 2 python type-hinting python-3.x

我有一个定义接口的 python 类

\n
class InterfaceFoo:\n    pass\n
Run Code Online (Sandbox Code Playgroud)\n

一些抽象类

\n
class AbstractBar:\n    pass\n
Run Code Online (Sandbox Code Playgroud)\n

也许还有一个具体的类

\n
class Bar(InterfaceFoo):\n    pass\n
Run Code Online (Sandbox Code Playgroud)\n

实现我的接口(或者,如果你愿意,接口Interface FooAbstractBar)。

\n

AbstractBar现在,在某些情况下,我希望有一个类型提示,表示 \xc2\xbb 我期望派生自 \xc2\xab并实现\xc2\xab的类的实例InterfaceFoo。我怎样才能在Python中做到这一点?当然,我可以检查我的代码并添加所有可能的具体类,这些类是其子类AbstractBar并实现InterfaceFoo,但我认为这是一个非常丑陋的解决方案。

\n
def func(obj: AbstractBar implementing InterfaceFoo):\n    pass\n
Run Code Online (Sandbox Code Playgroud)\n

这在其他语言中是可能的,例如 Objective-C,您可以在其中编写BaseClass<Interface>. 我想知道Python 中是否有类似的东西?

\n

Dan*_*erg 5

解决方案确实是typing.Protocol. 然而,这里的挑战在于,您需要名义子类型(即声明为从另一个类型继承的类型)和结构子类型(即类型具有特定接口)的组合。

Protocol的定义方式禁止在这里简单地使用多重继承,因为任何类型检查器都会很快告诉您,“协议的所有基础都必须是协议”。所以我们不能简单地继承 anyAbstractBar a ProtocolPEP 544的本节概述了有关此内容的详细信息。

然而,我们可以做的是将我们的抽象基类声明为一个协议。由于它是抽象的,因此不应该像协议一样直接实例化,因此这在大多数情况下应该有效。具体来说,对于abc标准库中的模块,我们可以选择指定抽象基类而不继承自,abc.ABC而是将元类设置为abc.ABCMeta

看起来可能是这样的:

from abc import ABCMeta, abstractmethod
from typing import Protocol

class AbstractBase(Protocol, metaclass=ABCMeta):
    @abstractmethod
    def foo(self) -> int:
        ...

class SomeInterface(Protocol):
    def bar(self) -> str:
        ...

class ABSomeInterface(AbstractBase, SomeInterface, Protocol):
    pass
Run Code Online (Sandbox Code Playgroud)

我们现在可以定义一个函数,它期望其参数的类型ABSomeInterface如下:

def func(obj: ABSomeInterface) -> None:
    print(obj.foo(), obj.bar())
Run Code Online (Sandbox Code Playgroud)

现在,如果我们想要实现一个具体的子类,AbstractBase并且我们希望该类符合我们的SomeInterface协议,我们还需要它实现一个bar方法:

class Concrete(AbstractBase):
    def foo(self) -> int:
        return 2

    def bar(self) -> str:
        return "x"
Run Code Online (Sandbox Code Playgroud)

Concrete现在我们可以安全地传递to的实例func并且类型检查器很高兴:

func(Concrete())
Run Code Online (Sandbox Code Playgroud)

相反,如果我们有另一个子类AbstractBase没有实现bar(因此没有遵循我们的SomeInterface协议),我们会得到一个错误:

class Other(AbstractBase):
    def foo(self) -> int:
        return 3

func(Other())
Run Code Online (Sandbox Code Playgroud)

mypy会这样抱怨:

error: Argument 1 to "func" has incompatible type "Other"; expected "ABSomeInterface"  [arg-type]
note: "Other" is missing following "ABSomeInterface" protocol member:
note:     bar
Run Code Online (Sandbox Code Playgroud)

这是我们所期望的,也是我们想要的。abc通过使用元类也保留了功能ABCMetaAbstractBase因此,尝试在不实现的情况下进行子类化foo会在读取该模块时导致运行时错误。

为了完整起见,这里有一个完整的工作示例,其中SomeInterface通用协议,以证明这也可以按预期工作:

error: Argument 1 to "func" has incompatible type "Other"; expected "ABSomeInterface"  [arg-type]
note: "Other" is missing following "ABSomeInterface" protocol member:
note:     bar
Run Code Online (Sandbox Code Playgroud)

值得注意的是,没有办法定义“纯粹”的SomeInterface协议。我们不能简单地拥有一个SomeInterface既可以实例化可以将其用作协议的类。这就是这些事物的本质。

据我所知,Python 中尚不存在这样的交集类型,因此这种结构方法是我们能做的最好的方法。