我来自Ruby,你可以非常轻松地进行方法链。让我们看一个例子。如果我想从列表中选择所有偶数并向其添加 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 语法看起来很糟糕。我尝试谷歌并没有找到任何好的答案。我所看到的只是如何使用自定义对象实现类似的效果,但没有任何方法可以以这种方式处理列表。我是不是错过了什么?
在调试控制台中时,在数组中获取一些 ActiveRecord 对象曾经非常有帮助,我可以直接链接方法来处理实体以进行调试。对于 Python,这似乎工作量太大了。
在 Ruby 中,每个可枚举对象都包含
Enumerable
接口,这就是为什么我们得到像你提到的所有这些有用的方法。但在 Python 中,可迭代对象没有通用的超类。可迭代的字面意思是“支持 __iter__
的事物”,虽然有一个名为 Iterable
的 抽象类,它假装是所有可迭代的超类,但它实际上不提供任何方法,并且不提供任何方法。不位于所有可迭代的继承链中(它使用 dunder 方法的魔力覆盖 isinstance
和 issubclass
的行为,就像您可以通过编写 +
来覆盖 __add__
一样)。
Alakazam 库正是实现了这个功能。 (披露:我是这个库的创建者和维护者,但它完全符合你的要求,所以我会在这里提到它)
Alakazam 提供了Alakazam
类,它包装了任何 Python 可迭代对象,并提供所有内置 Python 序列方法、所有
itertools
模块以及其他一些有用的面向流的方法(这些方法不是默认情况下包含在 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 的方法(如 filter
和
map
)就会懒惰地匹配 Python 的行为,因此我们仍然需要在末尾编写
list()
来消费可迭代对象并生成单个列表结果。
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]
。数据集很简单。但是想象一下
nums
是
list(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))
正如评论中所建议的,在 Ruby 中使用 #lazy
和范围可以很容易地获得这种对大数据集的惰性求值优势。
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
from streamable import Stream
nums = range(10)
# lazy operations
pair_nums_plus_5: Stream[int] = (
Stream(lambda: nums)
.filter(lambda n: n % 2 == 0)
.map(lambda n: n + 5)
)
print(list(pair_nums_plus_5))
[5, 7, 9, 11, 13]
Stream[T]
课程扩展
Iterable[T]