许多Python内置的"函数"实际上是类,尽管它们也有一个简单的函数实现。即使是非常简单的,比如itertools.repeat
。这样做的动机是什么?在我看来,这似乎是过度工程。
编辑:我不是在问itertools.repeat
的目的或任何其他特定功能。这只是一个非常简单的函数的例子,有一个非常简单的实现:
def repeat(x):
while True: yield x
但是itertools.repeat
实际上不是一个函数,它是作为一个类实现的。我的问题是:为什么?这似乎是不必要的开销。
我也理解类是可调用的函数,以及如何使用类来模拟类似函数的行为。但我不明白为什么它通过标准库被如此广泛地使用。
作为itertools
的类实现具有一些生成器函数所不具备的优点。例如:
- CPython在C层实现了这些内置功能,在C层,生成器"函数"最好实现为一个实现
__next__
的类,它将状态保存为实例属性;基于yield
的生成器是Python层的一个细节,实际上,它们只是generator
类的一个实例(所以它们实际上仍然是类实例,就像Python中的其他所有东西一样) - 生成器是不可pickleable或copyable的,并且没有"故事"使它们支持这两种行为(内部状态太复杂和不透明,无法泛化);一个类可以定义
__reduce__
/__copy__
/__deepcopy__
(如果它是一个Python级别的类,它可能甚至不需要这样做;它将自动工作)并使实例可pickleable/copyable(因此,如果您已经从range
迭代器生成了5个元素,您可以复制或pickle/unpickle它,并在迭代中获得相同距离的迭代器)
对于非生成器工具,原因通常类似。类可以被赋予状态和自定义行为,而函数却不能。它们可以从(如果需要的话,但是如果C层类是"逻辑上"的函数,则可以禁止子类化)。
对于动态实例创建也很有用;如果你有一个未知类的实例,但有一个已知的原型(比如,序列构造函数接受一个可迭代对象,或chain
或其他),你想把其他类型转换成那个类,你可以做type(unknown)(constructorarg)
;如果它是一个生成器,type(unknown)
是无用的,你不能用它来制造更多的自己,因为你不能自省找出它的来源(不合理的方式)。
除此之外,即使你从不使用编程逻辑的特性,你宁愿在交互式解释器中看到什么,或者在type(myiter)
, <class 'generator'>
的打印调试中看到没有提示来源,或者<class 'itertools.repeat'>
告诉你确切的东西和它来自哪里?
函数和类都是可调用对象,因此它们可以在高阶函数中互换使用,例如
$ 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
类为字典等定义了.get()
方法。
在itertools.repeat
(和大多数迭代器)的情况下,使用一个适当的类实现iterator
协议从实现/维护POV有一些好处——比如你可以更好地控制迭代,你可以专门化类等。我也怀疑有一些优化可以在c级对适当的迭代器进行,而不适用于生成器。
还要记住,类和函数也是对象——def
语句主要是用于创建function
实例并使用编译代码、本地名称空间、单元格、闭包和诸如此类的东西填充它的语法糖(这在某种程度上涉及到任务FWIW,我曾经只是出于好奇而做过一次,这是一个主要的PITA), class
语句也是用于创建新的type
实例的语法糖(手动执行它实际上是非常微不足道的)。从这个POV来看,yield
是一个类似的语法糖,它把你的函数变成一个返回通用generator
内置类型实例的工厂——它使你的函数像一个类,没有编写一个完整的类的麻烦,但也没有精细的控制和可能的优化,你可以通过编写一个完整的类。
在更一般的层面上,有时将你的"函数"写成一个自定义的可调用类型反而会提供类似的收益——精细的控制,可能的优化,有时只是更好的可读性(想想两步装饰器,自定义描述符等)。
最后wrt/内置类型(int
, str
等)IIRC(如果我错了,请有人纠正我)它们最初是作为工厂函数的函数(在新型类革命之前,内置类型和用户定义类型是不同类型的对象)。现在将它们作为普通类当然是有意义的,但是为了兼容性,它们必须保持all_lower命名方案。