使用 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 样式类型注释进行鸭子打字的主要内容,如果未能解决你的问题,请参考以下文章