Python流畅的过滤器,地图等

时间:2016-08-16 20:58:36

标签: python functional-programming fluent

我喜欢python。然而,有一点让我感到困惑的是,我不知道如何以流畅的方式格式化功能活动,就像javascript中的can一样。

示例(在现场随机创建):你能帮助我以流畅的方式将其转换为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.x中的1到5之间的每个偶数,但是Python 2它不会打印)。构建并返回列表也有点烦人。我宁愿只是为了循环。

6 个答案:

答案 0 :(得分:2)

尽管反对这样做的争论,这里是对你的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。

答案 1 :(得分:2)

理解是处理过滤器/地图操作的流畅的python方式。

您的代码类似于:

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

理解对于控制台日志记录这样的副作用效果不佳,所以在单独的循环中这样做。链接函数调用并不是python中常见的习惯用法。不要指望这是你的面包和黄油。 Python库倾向于遵循“更改状态或返回值,但不是两者”模式。存在一些例外情况。

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

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

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

Dict理解:`{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中取出第一个值,然后开始在范围对象上迭代,拉出一个值,将其平方,并将该值作为第一个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}

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

答案 2 :(得分:2)

生成器,迭代器和CREATE FUNCTION [hash].[HashDelimiter2]() RETURNS NCHAR(1) WITH SCHEMABINDING AS BEGIN RETURN N';' END GO /* This does indeed result in YES */ SELECT IS_DETERMINISTIC FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'HashDelimiter2' /* But then compile it native and it's no longer deterministic */ CREATE FUNCTION [hash].[HashDelimiter3]() RETURNS NCHAR(1) WITH NATIVE_COMPILATION, SCHEMABINDING AS BEGIN ATOMIC WITH ( TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English' ) RETURN N';' END GO /* This results in NO */ SELECT IS_DETERMINISTIC FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'HashDelimiter3' 为链接和过滤操作提供了额外的功能。但是,我不是记住(或查找)很少使用的东西,而是倾向于帮助函数和理解。

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

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)}

然后使用它进行dict comp:

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

答案 3 :(得分:1)

您编写的代码最大的破坏者是Python不支持多行匿名函数。 filtermap的返回值是一个列表,因此如果您愿意,可以继续链接它们。但是,您必须提前定义函数,或使用lambda。

答案 4 :(得分:1)

我现在正在寻找一个更贴近问题核心的答案:

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()
)

我现在正在尝试这个。如果上面显示的那样,今天将是非常愉快的一天。

更新:看一下:也许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应该被清理-但要点是有用的(无论如何对我来说)。

答案 5 :(得分:1)

已经有一个库可以完全满足您的需求,即流畅的语法,惰性求值和操作顺序与编写方式相同,还有其他许多好东西,例如多进程或多线程Map /降低。 它的名称为pyxtension,并且已经准备好并在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替换。