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

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

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

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

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

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

【问题讨论】:

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


    【解决方案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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-24
        • 2016-08-20
        • 2011-11-25
        • 2013-03-02
        • 2019-12-23
        • 2017-11-20
        相关资源
        最近更新 更多