使用 python 3.5 样式类型注释的鸭子打字
Duck typing with python 3.5 style type-annotations
假设我有一个签名如下的函数:
def foo(self, name:str, stream):
pass
我想为 "stream" 参数添加一个注释,这意味着“只要 x.readline()->str.
你就可以拥有任何对象 x
所以这意味着我可以在这里使用任何 python 文件对象作为参数(因为它有一个 readline 方法),但我也可以提供一个只实现 readline 的对象,这是完全可以接受的.
我如何重写这个函数定义以便我可以注释第二个参数?
此解决方案不等同于您正在寻找的解决方案:
you can have any object x as long as x.readline() -> str
相反,我们正在定义一个自定义抽象基础 class,它期望 readline
抽象方法由其子 classes 定义。因此,它不会接受任何随机对象,而是只接受这个新抽象基础的实例 class,使其更加明确。
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"
结构子类型(静态鸭子类型)由 PEP 544 https://www.python.org/dev/peps/pep-0544/ 提出。 If/when 接受您不需要显式子类化,您将能够简单地定义您自己的协议,静态类型检查器可以理解。
正如 ivanl 所说,PEP 544 adds Protocols to support 'static duck typing'. This PEP was accepted recently and was added in Python 3.8. You can also try Protocols in Python 3.6 and 3.7 with Mypy using the typing-extensions 包。
在您的情况下,您将使用单个方法定义一个非常简单的协议 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
假设我有一个签名如下的函数:
def foo(self, name:str, stream):
pass
我想为 "stream" 参数添加一个注释,这意味着“只要 x.readline()->str.
你就可以拥有任何对象 x所以这意味着我可以在这里使用任何 python 文件对象作为参数(因为它有一个 readline 方法),但我也可以提供一个只实现 readline 的对象,这是完全可以接受的.
我如何重写这个函数定义以便我可以注释第二个参数?
此解决方案不等同于您正在寻找的解决方案:
you can have any object x as long as
x.readline() -> str
相反,我们正在定义一个自定义抽象基础 class,它期望 readline
抽象方法由其子 classes 定义。因此,它不会接受任何随机对象,而是只接受这个新抽象基础的实例 class,使其更加明确。
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"
结构子类型(静态鸭子类型)由 PEP 544 https://www.python.org/dev/peps/pep-0544/ 提出。 If/when 接受您不需要显式子类化,您将能够简单地定义您自己的协议,静态类型检查器可以理解。
正如 ivanl 所说,PEP 544 adds Protocols to support 'static duck typing'. This PEP was accepted recently and was added in Python 3.8. You can also try Protocols in Python 3.6 and 3.7 with Mypy using the typing-extensions 包。
在您的情况下,您将使用单个方法定义一个非常简单的协议 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