使用 python 3.5 样式类型注释进行鸭子打字

Posted

技术标签:

【中文标题】使用 python 3.5 样式类型注释进行鸭子打字【英文标题】:Duck typing with python 3.5 style type-annotations 【发布时间】:2017-10-24 15:41:32 【问题描述】:

假设我有一个带有如下签名的函数:

def foo(self, name:str, stream):
    pass

我想为“stream”参数添加一个注释,这意味着“你可以拥有任何对象 x,只要 x.readline()->str.

这意味着我可以在这里使用任何 python 文件对象作为参数(因为它有一个 readline 方法),但我也可以提供一个只实现 readline 的对象,这是完全可以接受的。

我怎样才能重写这个函数定义,以便我可以注释第二个参数?

【问题讨论】:

【参考方案1】:

此解决方案并不等同于您正在寻找的解决方案:

你可以拥有任何对象 x,只要x.readline() -> str

相反,我们定义了一个自定义抽象基类,它期望 readline 抽象方法由其子类定义。因此,它不会接受任何随机对象,而是只接受这个新抽象基类的实例,使其更加明确。

from abc import ABC, abstractmethod

class FileObject(ABC):
    @abstractmethod
    def readline(self):
        raise NotImplementedError()

现在我们要定义一个自定义类型,它可以与 Python 的文件对象和 FileObject 的实例一起使用:

from typing import IO, TypeVar


StreamType = TypeVar('StreamType', IO, FileObject)
def func(name: str, stream: StreamType) -> None:
    pass

现在让我们使用mypy对其进行测试:

from io import StringIO, BytesIO


class X(FileObject):
    def readline(self):
        pass


func('a', StringIO())  # passed
func('a', BytesIO())  # passed
func('a', open('foo.txt'))  # passed
func('a', X())  # passed
func('a', object())  # failed
func('a', [])  # failed
func('a', 1)  # failed

输出:

$ mypy so.py
so.py:33: error: Type argument 1 of "func" has incompatible value "object"
so.py:34: error: Type argument 1 of "func" has incompatible value List[None]
so.py:35: error: Type argument 1 of "func" has incompatible value "int"

【讨论】:

您可能希望实现__subclasshook__,以便isinstance 检查在运行时通过。【参考方案2】:

结构子类型(静态鸭子类型)由 PEP 544 https://www.python.org/dev/peps/pep-0544/ 提出。如果/当它被接受时,您将不需要显式子类化,您将能够简单地定义自己的协议,静态类型检查器会理解这些协议。

【讨论】:

【参考方案3】:

正如 ivanl 所说,PEP 544 添加了协议以支持“静态鸭子类型”。这个 PEP 最近被接受并被添加到 Python 3.8 中。您还可以使用 typing-extensions 包在 Python 3.6 和 3.7 中使用 Mypy 尝试协议。

在您的情况下,您将使用单个方法定义一个非常简单的协议SupportsReadline,并在函数参数的注释中使用它:

# Python 3.8+, for 3.6 & 3.7 replace 'typing' with 'typing_extensions'.
from typing import Protocol

class SupportsReadline(Protocol):
    def readline(self) -> str:
        ...

def func(name: str, stream: SupportsReadline) -> None:
    pass

现在任何具有readline 方法和兼容签名的对象都是SupportsReadline 的隐式子类型,并且满足函数参数的注释。请注意,LineRepeater 不会显式继承自 SupportsReadline

class LineRepeater:
    def readline(self) -> str:
        return "Hello again!"

func("a", LineRepeater())  # OK

如果方法签名与完全匹配,其他对象也是如此:

from io import BytesIO, StringIO

func("a", StringIO())  # OK
func("a", open("foo.txt"))  # OK
func("a", BytesIO())  # ERROR (return type is bytes instead of str)
func("a", [])  # ERROR
func("a", 1)  # ERROR
func("a", object())  # ERROR

【讨论】:

以上是关于使用 python 3.5 样式类型注释进行鸭子打字的主要内容,如果未能解决你的问题,请参考以下文章

python 3.5代码中的变量需要类型注释

Python 3.5+中的递归类型[重复]

Python中鸭子类型

《游戏学习》| 射击类小游戏 html5 打野鸭子

鸭子类型

python 鸭子类型