Python 流畅的过滤器、地图等

Python fluent filter, map, etc

我爱python。但是,让我有点烦恼的一件事是我不知道如何像 javascript.

中的罐头一样以流畅的方式格式化功能活动

示例(现场随机创建):你能帮我把它转换成 python 吗?

var even_set = [1,2,3,4,5]
.filter(function(x){return x%2 === 0;})
.map(function(x){
    console.log(x); // prints it for fun
    return x;
})
.reduce(function(num_set, val) {
    num_set[val] = true;
}, {});

我想知道是否有流体选项?也许是图书馆。

总的来说,我对大多数事情都使用列表推导式,但如果我想打印它是一个真正的问题

例如,如何使用列表理解(Python 3 print() 作为函数但 Python 2 它没有)。构建并返回列表也有点烦人。我宁愿只是循环。

您编写的代码的最大问题是 Python 不支持多行匿名函数。 filtermap 的 return 值是一个列表,因此您可以根据需要继续链接它们。但是,您要么必须提前定义函数,要么使用 lambda。

尽管存在反对这样做的争论,但这里是对您的 JS 代码的 Python 的翻译。

from __future__ import print_function
from functools import reduce

def print_and_return(x):
    print(x)
    return x

def isodd(x):
    return x % 2 == 0

def add_to_dict(d, x):
    d[x] = True
    return d

even_set = list(reduce(add_to_dict,
                map(print_and_return,
                filter(isodd, [1, 2, 3, 4, 5])), {}))

它应该适用于 Python 2 和 Python 3。

理解是处理 filter/map 操作的流畅 python 方式。

你的代码应该是这样的:

def evenize(input_list):
    return [x for x in input_list if x % 2 == 0]

理解不能很好地处理控制台日志记录等副作用,因此请在单独的循环中进行。链接函数调用在 python 中并不是真正常见的习语。不要指望那会是你在这里的生计。 Python 图书馆倾向于遵循 "alter state or return a value, but not both" 模式。存在一些例外情况。

编辑: 从好的方面来说,python 提供了多种理解方式,非常棒:

列表理解:[x for x in range(3)] == [0, 1, 2]

集合理解:{x for x in range(3)} == {0, 1, 2}

字典理解:`{x: x**2 for x in range(3)} == {0: 0, 1: 1, 2: 4}

生成器理解(或生成器表达式):(x for x in range(3)) == <generator object <genexpr> at 0x10fc7dfa0>

通过生成器理解,尚未评估任何内容,因此它是防止在大型集合上进行流水线操作时内存使用量激增的好方法。

例如,如果您尝试执行以下操作,即使 range 具有 python3 语义:

for number in [x**2 for x in range(10000000000000000)]:
    print(number)

您将在尝试构建初始列表时遇到内存错误。另一方面,将列表理解更改为生成器理解:

for number in (x**2 for x in range(1e20)):
    print(number)

并且没有内存问题(运行 只需要很长时间)。发生的事情是范围对象被构建(它只存储开始、停止和步骤值(0、1e20 和 1))对象被构建,然后 for 循环开始遍历 genexp 对象。实际上,for 循环调用

GENEXP_ITERATOR = `iter(genexp)`
number = next(GENEXP_ITERATOR)
# run the loop one time
number = next(GENEXP_ITERATOR)
# run the loop one time
# etc.

(注意 GENEXP_ITERATOR 对象在代码级别不可见)

next(GENEXP_ITERATOR) 尝试从 genexp 中提取第一个值,然后开始迭代 range 对象,提取一个值,将其平方,并产生第一个值 number .下次 for 循环调用 next(GENEXP_ITERATOR) 时,生成器表达式从范围对象中提取第二个值,将其平方并在 for 循环的第二次传递中产生它。第一组数字不再保存在内存中。

这意味着无论生成器理解中有多少项,内存使用量都保持不变。您可以将生成器表达式传递给其他生成器表达式,并创建从不消耗大量内存的长管道。

def pipeline(filenames):
    basepath = path.path('/usr/share/stories')
    fullpaths = (basepath / fn for fn in filenames)
    realfiles = (fn for fn in fullpaths if os.path.exists(fn))
    openfiles = (open(fn) for fn in realfiles)
    def read_and_close(file):
        output = file.read(100)
        file.close()
        return output
    prefixes = (read_and_close(file) for file in openfiles)
    noncliches = (prefix for prefix in prefixes if not prefix.startswith('It was a dark and stormy night')
    return {prefix[:32]: prefix for prefix in prefixes}

在任何时候,如果您需要某种数据结构,您可以将生成器理解传递给另一种理解类型(如本示例的最后一行),此时,它将强制生成器评估他们留下的所有数据,但除非你这样做,否则内存消耗将仅限于单次通过生成器时发生的情况。

生成器、迭代器和 itertools 赋予链接和过滤操作更多的权力。但是,与其记住(或查找)很少使用的东西,我更倾向于帮助函数和理解。

例如在这种情况下,使用辅助函数处理日志记录:

def echo(x):
    print(x)
    return x

使用理解的 if 子句可以轻松选择偶数值。由于最终输出是字典,因此使用这种理解:

In [118]: d={echo(x):True for x in s if x%2==0}
2
4

In [119]: d
Out[119]: {2: True, 4: True}

或者要将这些值添加到现有字典中,请使用 update

new_set.update({echo(x):True for x in s if x%2==0})

另一种写法是使用中间生成器:

{y:True for y in (echo(x) for x in s if x%2==0)}

或者将回声和过滤器组合在一个生成器中

def even(s):
    for x in s:
        if x%2==0:
            print(x)
            yield(x)

后跟一个使用它的字典组合:

{y:True for y in even(s)}

我现在正在查看更接近问题核心的答案:

fluentpy https://pypi.org/project/fluentpy/ :

streams 程序员(在 scalajava、其他人)会喜欢这种集合方法链接:

import fluentpy as _
(
  _(range(1,50+1))
  .map(_.each * 4)
  .filter(_.each <= 170)
  .filter(lambda each: len(str(each))==2)
  .filter(lambda each: each % 20 == 0)
  .enumerate()
  .map(lambda each: 'Result[%d]=%s' %(each[0],each[1]))
  .join(',')
  .print()
)

而且效果很好:

Result[0]=20,Result[1]=40,Result[2]=60,Result[3]=80

我正在尝试这个。如果它像上面显示的那样工作,今天将是非常美好的一天。

更新:看看这个:也许 python 可以作为单行 shell 脚本开始变得更合理:

python3 -m fluentpy "lib.sys.stdin.readlines().map(str.lower).map(print)"

这是它在命令行上的作用:

$echo -e "Hello World line1\nLine 2\Line 3\nGoodbye" 
         | python3 -m fluentpy "lib.sys.stdin.readlines().map(str.lower).map(print)"

hello world line1

line 2

line 3

goodbye

还有一个额外的 newline 应该清理 - 但它的要点很有用(无论如何对我来说)。

Update 这是另一个 library/option :我改编自 gist 并且在 pipy 上可用 infixpy:

from infixpy import *
a = (Seq(range(1,51))
     .map(lambda x: x * 4)
     .filter(lambda x: x <= 170)
     .filter(lambda x: len(str(x)) == 2)
     .filter( lambda x: x % 20 ==0)
     .enumerate()                                            Ï
     .map(lambda x: 'Result[%d]=%s' %(x[0],x[1]))
     .mkstring(' .. '))
print(a)

有一个库已经完全满足您的需求,即流畅的语法、惰性计算和操作顺序与其编写方式相同,还有许多其他好东西,如多进程或多线程Map/Reduce。 它被命名为 pyxtension and it's prod ready and maintained on PyPi。 您的代码将以这种形式重写:

from pyxtension.strams import stream
def console_log(x):
    print(x)
    return x
even_set = stream([1,2,3,4,5])\
    .filter(lambda x:x%2 === 0)\
    .map(console_log)\
    .reduce(lambda num_set, val: num_set.__setitem__(val,True))

map 替换为 mpmap 表示多处理映射,或 fastmap 表示多线程映射。

我们可以为此使用Pyterator(免责声明:我是作者)。

我们定义打印的函数returns(不过我相信你完全可以省略)。

def print_and_return(x):
    print(x)
    return x

然后

from pyterator import iterate

even_dict = (
    iterate([1,2,3,4,5])
    .filter(lambda x: x%2==0)
    .map(print_and_return)
    .map(lambda x: (x, True))
    .to_dict()
)
# {2: True, 4: True}

我已将您的 reduce 转换为可以转换为字典的元组序列。