为什么很多 Python built-in/standard 库函数实际上 类

Why are many Python built-in/standard library functions actually classes

许多 Python 内置 "functions" 实际上是 classes,尽管它们也有一个简单的函数实现。即使是非常简单的,比如itertools.repeat。这样做的动机是什么?对我来说这似乎是过度设计。

编辑:我不是在询问 itertools.repeat 或任何其他特定功能的用途。这只是一个非常简单的函数的示例,具有非常简单的可能实现:

def repeat(x):
    while True: yield x

但是itertools.repeat实际上并不是一个函数,它被实现为一个class。我的问题是:为什么?这似乎是不必要的开销。

我还了解到 classes 是可调用函数以及如何使用 class 模拟类似函数的行为。但是我不明白为什么它通过标准库被广泛使用。

函数和 类 都是 callables,因此它们可以在高阶函数中互换使用,例如。

$ python2
... 
>>> map(dict, [["ab"], ["cd"], ["ef"]])
[{'a': 'b'}, {'c': 'd'}, {'e': 'f'}]
>>> map(lambda x: dict(x), [["ab"], ["cd"], ["ef"]])
[{'a': 'b'}, {'c': 'd'}, {'e': 'f'}]

也就是说,类 还可以定义 方法 ,您稍后可以在返回的对象上调用这些方法。比如字典的dict class defines the .get()方法等

itertools.repeat(和大多数迭代器)的情况下,使用适当的 class 实现 iterator 协议在实现/维护 POV 方面有一些优势 - 就像你可以更好地控制迭代,您可以专门化 class 等。我还怀疑可以在 C 级对不适用于生成器的适当迭代器进行一些优化。

还要记住 classes 和函数也是对象 - def 语句主要是用于创建 function 实例并使用编译代码、本地命名空间填充它的语法糖,单元格、闭包和诸如此类的东西(FWIW 的某种涉及任务,我只是出于好奇而做过一次,它是一个主要的 PITA),并且 class 语句也是用于创建新的 type 实例的语法糖(手动操作实际上是微不足道的)。从这个 POV 来看,yield 是一个类似的语法糖,它将你的函数变成一个工厂返回通用 generator 内置类型的实例 - IOW 它使你的函数像 class,没有写一个成熟的 class 很麻烦,但也没有精细的控制和可能的优化,你可以通过写一个成熟的 class.

在更一般的层面上,有时将 "function" 编写为自定义可调用类型反而会提供类似的收益 - 精细控制、可能的优化,有时只是更好的可读性(想想两步装饰器、自定义描述符等)。

最后 wrt/ 内置类型(intstr 等)IIRC(如果我错了,请有人纠正我)它们最初是充当工厂函数的函数(在新样式之前class当内置类型和用户定义类型是不同类型的对象时,这是一场革命)。现在将它们设为普通的 class 当然是有意义的,但为了兼容性,它们必须保留 all_lower 命名方案。

作为 itertools 的 class 实现具有一些生成器函数所没有的优势。例如:

  1. CPython 在 C 层实现这些内置函数,在 C 层,生成器 "function" 最好实现为 class 实现 __next__将状态保留为实例属性;基于 yield 的生成器是一个 Python 层,实际上,它们只是 generator class 的一个实例(所以它们实际上仍然是 class实例,就像 Python)
  2. 中的所有其他内容一样
  3. 生成器不可 pickleable 或可复制,并且没有 "story" 使它们支持任何一种行为(内部状态太复杂且不透明而无法概括); class 可以定义 __reduce__/__copy__/__deepcopy__(如果是 Python 级别 class,它可能甚至不需要这样做;它会自动工作)并生成实例 pickleable/copyable(因此,如果您已经从 range 迭代器生成了 5 个元素,您可以复制或 pickle/unpickle 它,并获得一个迭代器迭代中相同的距离)

对于非生成器工具,原因通常是相似的。 类 可以赋予函数无法赋予的状态和自定义行为。它们可以继承自(如果需要的话,但是如果它们是 "logically" 函数,C 层 classes 可以禁止 subclassing)。

它对于动态实例创建也很有用;如果你有一个未知 class 但已知原型的实例(例如,采用可迭代的序列构造函数,或 chain 或其他),并且你想将其他类型转换为该 class,你可以做到type(unknown)(constructorarg);如果它是一个生成器,type(unknown) 是没用的,你不能用它来制造更多的东西,因为你无法反省以找出它来自哪里(不是以合理的方式)。

除此之外,即使您从未将这些功能用于编程逻辑,您更希望在交互式解释器中看到什么,或者对 type(myiter)<class 'generator'> 进行打印调试而没有给出任何提示到 origin,或者 <class 'itertools.repeat'> 准确地告诉你你拥有什么以及它来自哪里?