【问题标题】:Why are reversed and sorted of different types in Python?为什么在Python中对不同类型进行反转和排序?
【发布时间】:2018-01-22 05:21:33
【问题描述】:

reversed的类型是“类型”:

>>> type(reversed)
<class 'type'>

sorted的类型是“内置函数或方法”:

>>> type(sorted)
<class 'builtin_function_or_method'>

但是,它们在本质上似乎相同。排除功能上的明显差异(反转与排序顺序),实现这种差异的原因是什么?

【问题讨论】:

  • 他们是不同的,因为他们做不同的事情。最终结果可能看起来很相似,但获得最终结果的方式却大不相同。
  • 毫无根据的推测:reversed 是一种类型,因为它返回一个实现 __length_hint__ 方法的迭代。由于这个方法必须在一个类中定义,所以reversed 本身就是那个类是有意义的。 (这个理论的问题在于 __length_hint__ 只存在于 python 3.4 之后)
  • 让我问一个相反的问题:你为什么期望它们是一样的?

标签: python types python-internals


【解决方案1】:

区别在于reversed 是一个迭代器(它也是惰性求值),而sorted 是一个“热切”工作的函数。

mapzipfilterreversed 等所有内置迭代器(至少在 python-3.x 中)都实现为 。虽然急切操作的内置函数是 functions,例如minmaxanyallsorted

>>> a = [1,2,3,4]
>>> r = reversed(a)
<list_reverseiterator at 0x2187afa0240>

您实际上需要“使用”迭代器来获取值(例如list):

>>> list(r)
[4, 3, 2, 1]

另一方面,像sorted这样的函数不需要这个“消耗”部分:

>>> s = sorted(a)
[1, 2, 3, 4]

在 cmets 中,有人问为什么将这些实现为类而不是函数。这不是很容易回答,但我会尽力而为:

使用惰性求值操作有一个巨大的好处:它们在链接时非常节省内存。他们不需要创建中间列表,除非他们被明确“请求”。这就是为什么 mapzipfilter 从急切操作函数 (python-2.x) 更改为惰性操作类 (python-3.x) 的原因。

Python中创建迭代器一般有两种方式:

  • return self 在其__iter__ 方法中的类
  • 生成器函数 - 包含 yield 的函数

但是(至少是 CPython)在 C 中实现了它们的所有内置函数(和几个标准库模块)。在 C 中创建迭代器类非常容易,但我还没有找到任何明智的方法来创建基于Python-C-API。因此,这些迭代器被实现为类(在 CPython 中)的原因可能只是方便或缺乏(快速或可实现的)替代方案。

使用类而不是生成器还有一个原因:您可以为类实现特殊方法,但不能在生成器函数上实现它们。这听起来可能并不令人印象深刻,但它具有明显的优势。例如,大多数迭代器可以是 pickled(至少在 Python-3.x 上)使用 __reduce____setstate__ 方法。这意味着您可以将它们存储在磁盘上,并允许复制它们。由于 Python-3.4 一些迭代器也实现了__length_hint__,这使得使用list(和类似的)的迭代器更快。


注意reversed 可以很容易地实现为工厂函数(如iter),但与iter 不同,它可以返回两个唯一 类,reversed 只能返回一个独特的类。

为了说明可能的(和唯一的)类,您必须考虑一个没有__iter____reversed__ 方法但可迭代和反向迭代的类(通过实现__getitem____len__):

class A(object):
    def __init__(self, vals):
        self.vals = vals

    def __len__(self):
        return len(self.vals)

    def __getitem__(self, idx):
        return self.vals[idx]

虽然在iter 的情况下添加抽象层(工厂函数)是有意义的 - 因为返回的类取决于输入参数的数量:

>>> iter(A([1,2,3]))
<iterator at 0x2187afaed68>
>>> iter(min, 0)   # actually this is a useless example, just here to see what it returns
<callable_iterator at 0x1333879bdd8>

该推理不适用于reversed

>>> reversed(A([1,2,3]))
<reversed at 0x2187afaec50>

【讨论】:

  • 无论如何 +1,itertools. 方法也被实现为类,遵循我猜的相同逻辑,虽然我仍然不清楚为什么
  • 我稍微更新了答案,以包括为什么这些是 CPython 中的类的一个原因。然而,对于reversed,不可能以有意义的方式回答这个问题,因为这也很容易成为工厂函数(如iter)。但就像iter 一样,reversed 的返回值将始终是迭代器类或生成器函数。
  • 在此处添加一些内容:请记住,惰性求值需要某种容器对象来存储有关当前状态的信息。这只能通过一个对象来完成。因此,一个类。
  • @cᴏʟᴅsᴘᴇᴇᴅ 不一定,您也可以使用生成器,此时typefunction,函数的返回值为generator。请注意,由于 Python 的“一切都是对象”,一切(甚至函数)都是类。
  • @cᴏʟᴅsᴘᴇᴇᴅ:“函数必须构造某个类的某个实例。” - 是的,但sorted 也是如此,任何返回新对象的函数也是如此。
【解决方案2】:

reversedsorted 有什么区别?

有趣的是,reversed 不是函数,而 sorted 是。

打开一个 REPL 会话并输入 help(reversed):

class reversed(object)
 |  reversed(sequence) -> reverse iterator over values of the sequence
 |  
 |  Return a reverse iterator

它确实是一个用于返回反向迭代器的类。

好的,所以reversed 不是函数。但为什么不呢?

这有点难以回答。一种解释是迭代器具有惰性求值。这需要某种容器在任何给定时间存储有关迭代器当前状态的信息。这最好通过一个对象来完成,因此需要一个class

【讨论】:

  • 如果我正确理解了 OP 的问题,他们会问 为什么 reversed 不是函数。 (或者为什么 sorted 不是类型。)
  • 文档似乎有点误导,reversed(和其他类)列在“内置函数”docs.python.org/3/library/functions.html
猜你喜欢
  • 2010-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多