【问题标题】:Why are many Python built-in/standard library functions actually classes为什么许多 Python 内置/标准库函数实际上是类
【发布时间】:2017-03-03 13:37:18
【问题描述】:

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

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

def repeat(x):
    while True: yield x

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

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

【问题讨论】:

  • itertools 是用 C 语言实现的,而不是 Python。
  • 你能举一些更清楚的例子吗?
  • 你想到的所有案例都用C实现了吗?还是有一些是原生 Python?

标签: python class standard-library


【解决方案1】:

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

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

对于非生成器工具,原因通常是相似的。类可以被赋予函数不能的状态和自定义行为。它们可以继承自(如果需要,但如果 C 层类是“逻辑”函数,则可以禁止子类化)。

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

除此之外,即使您从未将这些功能用于编程逻辑,您更愿意在交互式解释器中看到什么,或者对type(myiter)<class 'generator'> 进行打印调试(没有提供有关来源的提示)或@987654334 @ 可以准确地告诉你你拥有什么以及它来自哪里?

【讨论】:

    【解决方案2】:

    函数和类都是可调用对象,因此它们可以在高阶函数中互换使用。

    $ 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'在不用于实例化对象的情况下被实现为一个类?
    • 对不起-澄清我的评论-我认为您需要扩展答案,以便不太专业的读者可以理解在给定示例中它是有意义的,因为itertools.repeat 类返回itertools.repeat调用时的对象。
    • @strubbly itertools.repeat 返回一个迭代器,即支持迭代协议的类的实例。
    • 是的,但令人惊讶的是,它是 '' 的对象,而不是例如 '' 的对象。
    【解决方案3】:

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

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

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

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

    【讨论】:

    • 我认为您的意思是 class 语句是创建 type 对象的语法糖。没有class 对象,只有types。
    猜你喜欢
    • 2019-05-05
    • 1970-01-01
    • 1970-01-01
    • 2010-12-31
    • 2017-01-04
    • 1970-01-01
    • 2018-02-21
    • 2010-12-10
    • 2020-02-07
    相关资源
    最近更新 更多