【问题标题】:Type hinting vs duck typing类型提示与鸭子类型
【发布时间】:2020-08-05 01:30:54
【问题描述】:

在 Python 中使用类型提示的一个缺点是牺牲了 Python 代码的美感。

在类型提示之前,我的方法签名很简洁:

def echo(items):
    for i in items:
        print(i)
    

由于我的团队正在使用类型提示,因此我也在我的代码中添加了类型提示:

def echo(items: Set[str]) -> None:

仍然很清晰。一段时间后,我的代码中在集合上运行的其他部分要求我的items 是可散列的,而其他部分则不需要。所以我决定也支持frozenset,现在我的方法看起来像:

 def echo(items: Union[Set[str],Frozenset[str]]) -> None:
 

它开始看起来像 Java 中的方法,尽管在 Java 中我可以操作接口,忽略实现细节:

 void echo(Set<String> items) {

Python 不支持接口概念,即我不能说Set 实现了Frozenset。由于鸭子类型,初始实现对SetFrozenset 都有效:两者的行为都与设置相同。但是,我的印象是,显式类型提示在某种程度上与鸭子类型不兼容

如何在打字提示和鸭式打字之间找到一个很好的平衡?

【问题讨论】:

  • 我让其他人详细说明,但至少有typing.AbstractSet可以解决接口问题。
  • 这不是鸭子打字的真正问题,通过Protocols 支持。这只是使用适当的共同祖先而不是枚举类的问题。

标签: python type-hinting


【解决方案1】:

使用AbstractSet:

from typing import AbstractSet

def echo(items: AbstractSet[str]) -> None:
    ...

SetFrozenSet 都(直接或间接)继承自 AbstractSet

AbstractSet
    |
    |
    +--------------+
    |              |
    |              |
MutableSet        FrozenSet
    |
    |
   Set

【讨论】:

  • 您能否提供一个指向明确实现此关系的 Python 代码库的链接? Java JDK 包含类似public class HashSet&lt;T&gt; implements Set&lt;T&gt; 的内容。看着typing.Frozenset我只能看到FrozenSet = _alias(frozenset, T_co, inst=False)
  • 它是从docs.python.org/3/library/typing.html 明确记录的父类中收集的。
【解决方案2】:

正如@chpner 所说,使用内置类型是一个不错的选择。从 Python 3.7 开始,类型模块中的所有抽象类型都是 protocols

协议的概念与接口有一些相似之处。

如文档中所述:

结构子类型可以看作是鸭子类型的静态等价物,这是 Python 程序员所熟知的。 Mypy 通过下面描述的协议类提供对结构子类型的支持。有关 Python 中协议和结构子类型的详细规范,请参阅PEP 544

也可以定义自定义协议:

from typing import Iterable
from typing_extensions import Protocol

class SupportsClose(Protocol):
    def close(self) -> None:
       ...  # Empty method body (explicit '...')

class Resource:  # No SupportsClose base class!
    # ... some methods ...

    def close(self) -> None:
       self.resource.release()

def close_all(items: Iterable[SupportsClose]) -> None:
    for item in items:
        item.close()

close_all([Resource(), open('some/file')])  # Okay!

虽然协议的主要目的是静态分析,但它们允许检查对象在运行时是否也遵循某些协议:

from typing_extensions import Protocol, runtime_checkable

@runtime_checkable
class Portable(Protocol):
    handles: int

class Mug:
    def __init__(self) -> None:
        self.handles = 1

mug = Mug()
if isinstance(mug, Portable):
   use(mug.handles)  # Works statically and at runtime

【讨论】:

  • 所以协议看起来有点像 Rust 中的特征。我需要阅读更多内容。谢谢。
  • 特质其实并不是一个新概念。但它们有点不同。 Go 在方法上使用结构类型来确定类型与接口的兼容性。 en.wikipedia.org/wiki/Structural_type_system
  • “协议的概念与接口有一些相似之处。” – 不仅仅是相似之处。他们是一样的。 协议是OO中的一个基本概念。请记住,OO 的基础是messaging,网络隐喻无处不在。 (Alan Kay 认识在他发明 Smalltalk 的同时发明互联网的人,并对他们的工作非常感兴趣。对象 == 网络主机、方法 == 隐藏本地代码、字段 == 隐藏本地存储的想法,message == public API 在 Smalltalk 的设计中非常明显。)一开始,……的想法
  • "protocol" 只是非正式的,但很快就正式集成到 Smalltalk IDE 中。然后研究了第一个 Smalltalk 类型系统,这些系统也很大程度上基于网络规范和网络协议的思想。当 Brad Cox 和 Tom Love 将 Smalltalk 对象模型放到 C 中并创建了 Objective-C 时,他们将协议的想法变成了语言的一部分。 Objective-C 是对 Java 设计的主要影响,Java 中的interface 关键字直接来自于 Objective-C 协议。所以,我们可以从 Smalltalk 中的协议和 Java 中的接口画一条直线。
猜你喜欢
  • 2015-05-21
  • 2018-10-11
  • 1970-01-01
  • 1970-01-01
  • 2011-03-23
  • 1970-01-01
  • 1970-01-01
  • 2021-06-27
  • 2011-12-24
相关资源
最近更新 更多