我们可以在列表上进行方法链吗?

Can we method chain on lists?

我来自Ruby,你可以很容易地进行方法链。让我们看一个例子。如果我想 select 列表中的所有偶数并向其添加 5。我会在 Ruby.

中做这样的事情
nums = [...]
nums.select {|x| x % 2 == 0 }.map { |x| x + 5 }

在 Python 中变为

nums = [...]
list(map(lambda x: x + 5, filter(lambda x: x % 2 == 0, nums)))

Python 语法看起来很糟糕。我尝试 Google 并没有真正找到任何好的答案。我所看到的只是如何使用自定义对象实现类似的功能,但无法以这种方式处理列表。我错过了什么吗?

在调试控制台中时,获取数组中的一些 ActiveRecord 对象曾经非常有用,我可以只链接方法来处理实体以进行调试。使用 Python,工作量似乎太大了。

在 Ruby 中,每个可枚举对象都包含 Enumerable 接口,这就是为什么我们得到了所有这些有用的方法,就像你提到的那样。但是在 Python 中,可迭代对象没有通用的 superclass。可迭代对象的字面定义是“支持 __iter__ 的东西”,虽然有一个 abstract class called Iterable 假装是所有可迭代对象的超级 class,但它实际上并不提供任何方法并且它不位于所有可迭代对象的继承链中(它使用 dunder 方法的魔力覆盖 isinstanceissubclass 的行为,就像您可以通过以下方式覆盖 +写作 __add__).

Alakazam 库实现了这个特性。 (披露:我是这个库的创建者和维护者,但它完全符合你的要求,所以我会在这里提到它)

Alakazam 提供 Alakazam class,它包装任何 Python 可迭代对象,并提供所有 built-in Python 序列方法作为方法,所有 itertools 模块,以及一些其他有用的 stream-oriented 方法,默认情况下不包含在 Python 中。考虑上面的例子

nums.select {|x| x % 2 == 0 }.map { |x| x + 5 }

在Python中,看起来像

list(map(lambda x: x + 5, filter(lambda x: x % 2 == 0, nums)))

对于 Alakazam,看起来像

zz.of(nums).filter(lambda x: x % 2 == 0).map(lambda x: x + 5).list()

或者,使用 Alakazam 的 lambda 语法

zz.of(nums).filter(_1 % 2 == 0).map(_1 + 5).list()

在合理的情况下,Alakazam 的方法如 filtermap 懒惰地匹配 Python 的行为,所以我们仍然需要在最后写 list() 以使用可迭代对象并生成单个列表结果。

如评论中所述,此 Ruby 代码:

nums = [...]
nums.select {|x| x % 2 == 0 }.map { |x| x + 5 }

注意:为什么不用#even?

nums = [...]
nums.select {|x| x.even? }.map { |x| x + 5 }

甚至:

nums = [...]
nums.select(&:even?).map { |x| x + 5 }

但撇开吹毛求疵不谈,这可以用 Python 使用列表理解来表达,这非常干净。

nums = [...]
[x + 5 for x in nums if x % 2 == 0]

现在,列表理解会急切地生成完整列表。想象一下像 [1, 2, 3, 4, 5, 6, 7, 8] 这样的原始列表。列表理解会给我们 [2, 4, 6, 8]。数据集很简单。

但是假设 numslist(range(100_000_000))不是 一个微不足道的数据集。将这个列表理解应用于整个事情会花费很多时间,即使我们只需要前四个值。

但是生成器表达式让我们懒惰地生成我们需要的值。

from itertools import islice

nums = range(100_000_000)
evens_plus_five = (x + 5 for x in nums if x % 2 == 0)

list(islice(evens_plus_five, 0, 5, 1))

正如评论中所建议的那样,使用 #lazy 和范围可以很容易地在 Ruby 中获得这种对大型数据集的惰性评估优势。

nums = (1..100_000_000)
nums.lazy.select(&:even?).map { |x| x + 5 }.take(5).to_a

如果您使用的是 Ruby 3,让我们让那个块更干净。

nums = (1..100_000_000)
nums.lazy.select(&:even?).map { _1 + 5 }.take(5).to_a