如何记录鸭子类型?

16 投票
3 回答
1092 浏览
提问于 2025-04-17 05:55

我遇到了文档膨胀的问题。每当我碰到复杂的鸭子类型时,我需要一种方式来说明“这个鸭子类型”,但结果却陷入了一个无尽的循环:我的函数需要这个输入,但文档里没有说明,然后我又得去写文档。这导致了冗长且重复的文档,比如下面这样:

def Foo(arg):
    """
    Args:
      arg: An object that supports X functionality, and Y functionality,
        and can be passed to Z other functionality.
    """
    # Insert code here.

def Bar(arg):
    """
    Args:
      arg: An object that supports X functionality, and Y functionality,
        and can be passed to Z other functionality.
    """
    # Insert code here.

接下来还有 BazQux 和其他函数。我需要一种更简短的方式来写“arg 是一个(对象的类型)”。

对于某些鸭子类型,像“一个字典样的对象”就很简单:我们知道字典应该是什么样的,所以我们知道该传什么。一个 dict,或者是能模仿它的东西。

我觉得 C++ 在模板类型上也有这个问题。Haskell 也会有这个问题,但可以用类型类的定义来进行文档说明。(注意:Haskell 的类和 Java/C++/Python 等的类不一样。)(注意:我其实不太会用 Haskell,所以如果这个例子不太好,请见谅。)

我是不是应该走传统的面向对象路线,写一个基类,然后在文档里说“任何像这个基类的东西”?代码并不会强制要求从基类派生(因为对象不需要从它派生),而且基类除了记录接口的属性外,基本上没有其他价值。

另一方面,我在编写 Python 代码,尽量按照语言的习惯来编程。(因为不这样做通常会带来麻烦。)基类在继承功能上是不错的,但当你的基类完全抽象时,在一个鸭子类型的语言里似乎没有什么价值。


编辑:关于回答:我知道什么是鸭子类型(这应该从我的帖子中可以看出来)。我的问题是,在哪里记录它,尤其是当没有类可以附加文档时。

3 个回答

1

一些类型比较严格的编程语言,比如Java,有一个叫“接口”的概念:就是一组方法,任何实现这个接口的类都应该提供这些方法。

其实你可以借用这个概念,但不一定要那么严格:你可以定义一个抽象类 Foo,然后说明你的方法需要“一个 Foo 或者一个类似 Foo 的对象”。如果你不想让其他类继承 Foo,也没关系;看文档的人依然能明白什么是 Foo 类似的对象,以及需要提供什么。

2

鸭子类型的意思是,“类型”这个概念变得更加抽象和直观,而不是语言中正式的一部分。这让类型变得比那些类型检查是语言一部分的语言更加灵活和自由。

使用鸭子类型时,程序本身并不需要知道你用的是什么“类型”,而是其他程序员需要知道。所以,如果你有一整套类、函数等,它们是针对某种特定“类型”的对象工作的,而这个类型又不能用几句话简单描述,那你可以在注释或者文档字符串中(甚至是外部的.txt文件)添加一段描述你的类型和给它起个名字。这样你就可以在任何地方引用这个名字了。

8

鸭子类型的意思是,你在文档中说明你期待得到一个“鸭子”,而其他对象则需要假装成“鸭子”。

在文档中没有任何API明确说明它接受一个StringIO对象;但是,我们可以在大多数需要“像文件一样的对象”的地方使用它。

而且,标准库大部分情况下也不具体说明“鸭子类型”需要哪些特定的方法。这让实现的方式可以灵活变化。比如,random.sample这个API,可以用可迭代对象或者序列来定义。

如果你想更具体一点,可以使用抽象基类。在collections模块中已经包含了几个(比如Iterable、Hashable和Sized),在numbers模块中也有(比如Rational、Integral等)。根据这些来写你自己的抽象基类并不难。然后,文档中只需要提到哪些抽象基类是必须的(例如,x是一个Sized Iterable,而y是一个Integral)。

撰写回答