为什么很多 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/ 内置类型(int
、str
等)IIRC(如果我错了,请有人纠正我)它们最初是充当工厂函数的函数(在新样式之前class当内置类型和用户定义类型是不同类型的对象时,这是一场革命)。现在将它们设为普通的 class 当然是有意义的,但为了兼容性,它们必须保留 all_lower 命名方案。
作为 itertools
的 class 实现具有一些生成器函数所没有的优势。例如:
- CPython 在 C 层实现这些内置函数,在 C 层,生成器 "function" 最好实现为 class 实现
__next__
将状态保留为实例属性;基于 yield
的生成器是一个 Python 层,实际上,它们只是 generator
class 的一个实例(所以它们实际上仍然是 class实例,就像 Python) 中的所有其他内容一样
- 生成器不可 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'>
准确地告诉你你拥有什么以及它来自哪里?
许多 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/ 内置类型(int
、str
等)IIRC(如果我错了,请有人纠正我)它们最初是充当工厂函数的函数(在新样式之前class当内置类型和用户定义类型是不同类型的对象时,这是一场革命)。现在将它们设为普通的 class 当然是有意义的,但为了兼容性,它们必须保留 all_lower 命名方案。
作为 itertools
的 class 实现具有一些生成器函数所没有的优势。例如:
- CPython 在 C 层实现这些内置函数,在 C 层,生成器 "function" 最好实现为 class 实现
__next__
将状态保留为实例属性;基于yield
的生成器是一个 Python 层,实际上,它们只是generator
class 的一个实例(所以它们实际上仍然是 class实例,就像 Python) 中的所有其他内容一样
- 生成器不可 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'>
准确地告诉你你拥有什么以及它来自哪里?