鸭子类型说 "如果它像鸭子一样叫,就把它当成鸭子。"类型检查似乎与鸭子类型不一致:我们把变量限制在命名的类型(类)上,只允许该类型或子类型。 这似乎会阻止我们传递任意的对象,这些对象可以 "以正确的方式叫"。 但自从PEP 0544以来,我们可以用以下方式声明 "鸭子式 "类型 typing.Protocol.
以此为例。
class Duck:
def quack(self) -> str:
return "Quack."
def sonorize(duck: Duck) -> None:
print(duck.quack())
sonorize(Duck())
这里我们有一个vanillaDuck 类和一个接受该类的函数。sonorize() 是相当限制性的,可以通过鸭子类型更好地服务于用户。它可以接受任何具有正确类型的quack() 方法,并且功能很好。
指定的方式是用一个 typing.Protocol一个协议类型包含一组类型化的方法和变量。 如果一个对象有这些方法和变量,它将与协议类型相匹配。
typing.Protocol 是在 Python 3.8 中添加的。 在之前的 Python 版本中,你可以使用 typing_extensions.Protocol,并在升级时进行迁移。Mypy从0.530(2017年10月)版本开始支持Protocol 。
在我们的例子中,我们可以为可以呱呱叫的对象定义一个协议。
from typing import Protocol
class Quacker(Protocol):
def quack(self) -> str:
...
我们使用class 语法来定义一个协议,并在主体中添加带有类型提示的相关成员。这些方法将永远不会被执行,所以在主体中使用 Python 的省略号是标准的。
定义了协议后,我们可以在sonorize() 中使用它来匹配任何具有返回字符串的quack() 方法的对象。 要把它组合成一个完整的例子。
from typing import Protocol
class Quacker(Protocol):
def quack(self) -> str:
...
class Duck:
def quack(self) -> str:
return "Quack."
class MegaDuck:
def quack(self) -> str:
return "QUACK!"
def sonorize(duck: Quacker) -> None:
print(duck.quack())
sonorize(Duck())
sonorize(MegaDuck())
Python 文档称使用Protocol 结构子类型,意味着类型检查器根据它们的内部结构来比较类型。这与名义子类型相反,意味着类型检查器根据它们是否是命名的类型或子类来比较类型。
Python 的collections.abc 模块中的抽象基类对于常见的协议检查非常有用。例如,你可以使用Sized 来接受任何与len() 配合的类型,而Iterable[int] 则接受任何整数的可迭代类型。