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

时间:2017-05-24 10:37:14

标签: python python-3.5 python-3.6 type-hinting

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

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

我想在" stream"中添加注释。参数,这意味着"你可以拥有任何对象x只要x.readline() - > str。

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

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

3 个答案:

答案 0 :(得分:6)

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

答案 1 :(得分:3)

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

  

只要x.readline() -> str

,您就可以拥有任何对象x

相反,我们定义了一个自定义抽象基类,它希望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"

答案 2 :(得分:0)

正如ivanl所指出的,the online version here添加了协议来支持“静态鸭子类型”。该PEP最近已被接受,并将成为Python 3.8的一部分。您已经可以使用PEP 544软件包在Mypy上尝试使用Protocols(在Python 3.6+中)。

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

# In 3.8 this can become 'from typing ...'
from typing_extensions 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