在 Python 3 中的列表上从左到右应用操作

Left to right application of operations on a list in Python 3

有什么方法可以在 Python 中的列表上实现从左到右的非惰性调用操作?

例如斯卡拉:

 val a = ((1 to 50)
  .map(_ * 4)
  .filter( _ <= 170)
  .filter(_.toString.length == 2)
  .filter (_ % 20 == 0)
  .zipWithIndex
  .map{ case(x,n) => s"Result[$n]=$x"}
  .mkString("  .. "))

  a: String = Result[0]=20  .. Result[1]=40  .. Result[2]=60  .. Result[3]=80

虽然我知道很多人不喜欢上面的语法,但我喜欢从左到右移动并添加任意操作的能力。

Python for 当有三个或更多操作时,理解是 IMO 不容易阅读。结果似乎是我们需要将所有内容分解成块。

[f(a) for a in g(b) for b in h(c) for ..]

提到的方法有机会吗?

注意:我尝试了一些库,包括 toolz.functoolz。 Python 3 懒惰评估:每个级别 returns 一个 map 对象。此外,它是否可以对输入 list.

进行操作并不明显

即使它不被认为是 Pythonic,Python 仍然包含 mapfilter 并且 reduce 可以从 [=17= 导入].使用这些函数可以生成与 Scala 中相同的管道线,尽管它的书写方向相反(从右到左,而不是从左到右):

from functools import reduce
a = reduce(lambda f,s: f'{f} .. {s}',
    map(lambda nx: f'Result[{nx[0]}]: {nx[1]}',
    enumerate(
    filter(lambda n: n%20 == 0,
    filter(lambda n: len(str(n)) == 2,
    filter(lambda n: n <= 170,
    map(lambda n: n*4,
    range(1,51))))))))

现在,这是惰性的,因为它会让每个值在计算下一个值之前通过整个管道传输。但是,由于最终的 reduce 调用消耗了所有值,因此看不到。

可以在每个步骤中从每个 mapfilter 对象生成一个列表:

a = reduce(lambda f,s: f'{f} .. {s}',
    list(map(lambda nx: f'Result[{nx[0]}]: {nx[1]}',
    list(enumerate(
    list(filter(lambda n: n%20 == 0,
    list(filter(lambda n: len(str(n)) == 2,
    list(filter(lambda n: n <= 170,
    list(map(lambda n: n*4,
    list(range(1,51)))))))))))))))

这两个表达式,尤其是第二个,都比较冗长,所以我不知道我是否会推荐它们。我建议使用 list/generator 理解和一些中间变量:

n4 = [n*4 for n in range(1,51)]
fn4 = [n for n in n4 if n <= 170 if len(str(n))==2 if n%20 == 0]
rfn4 = [f'Result[{n}]: {x}' for n, x in enumerate(fn4)]
a = ' .. '.join(rfn4)

这种方法的另一个好处(至少对您而言)是,使用这种方法您将保持在 Scala 中找到的操作顺序。它也将,只要我们做列表理解(如图所示)non-lazy 评估。如果我们想要惰性求值,可以改为进行生成器理解:

n4 = (n*4 for n in range(1,51))
fn4 = (n for n in n4 if n <= 170 if len(str(n))==2 if n%20 == 0)
rfn4 = (f'Result[{n}]: {x}' for n, x in enumerate(fn4))
a = ' .. '.join(rfn4)

因此,唯一的区别是我们使用括号而不是方括号。但是,如前所述;由于所有数据都已消耗,因此此示例中的差异很小。

@JohanL 的回答很好地了解了标准 python 库中最接近的等价物。

我最终在 2019 年 11 月改编了 Matt Hagy 的要点,现在 pypi

https://pypi.org/project/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)

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

其他答案中描述的其他方法

  • pyxtension

    从pyxtension.streams导入流

  • sspipe

    从 sspipe 导入 p, px

旧方法

我在 2018 年秋季发现了一个更具吸引力的工具包

https://github.com/dwt/fluent

在对可用的 第三方 库进行相当彻底的审查后,似乎 Pipe https://github.com/JulienPalard/Pipe 最适合需要。

您可以创建自己的管道函数。我用它来处理下面显示的一些文本。粗线是工作发生的地方。所有这些 @Pipe 的东西我只需要编码一次然后就可以 re-use.

这里的任务是关联第一个文本中的缩写:

rawLabels="""Country: Name of country
Agr: Percentage employed in agriculture
Min: Percentage employed in mining
Man: Percentage employed in manufacturing
PS: Percentage employed in power supply industries
Con: Percentage employed in construction
SI: Percentage employed in service industries
Fin: Percentage employed in finance
SPS: Percentage employed in social and personal services
TC: Percentage employed in transport and communications"""

第二个文本中有关联标签:

mylabs = "Country Agriculture Mining Manufacturing Power Construction Service Finance Social Transport"

这是功能操作的one-time编码(在后续管道中重用):

@Pipe
def split(iterable, delim= ' '):
    for s in iterable: yield s.split(delim)

@Pipe
def trim(iterable):
    for s in iterable: yield s.strip()

@Pipe
def pzip(iterable,coll):
    for s in zip(list(iterable),coll): yield s

@Pipe
def slice(iterable, dim):
  if len(dim)==1:
    for x in iterable:
      yield x[dim[0]]
  elif len(dim)==2:
    for x in iterable:
      for y in x[dim[0]]:
        yield y[dim[1]]
    
@Pipe
def toMap(iterable):
  return dict(list(iterable))

这里是大结局 结局 :全部在一个管道中:

labels = (rawLabels.split('\n') 
     | trim 
     | split(':')
     | slice([0])
     | pzip(mylabs.split(' '))
     | toMap )

结果:

print('labels=%s' % repr(labels))

labels={'PS': 'Power', 'Min': 'Mining', 'Country': 'Country', 'SPS': 'Social', 'TC': 'Transport', 'SI': 'Service', 'Con': 'Construction', 'Fin': 'Finance', 'Agr': 'Agriculture', 'Man': 'Manufacturing'}

这是另一个使用 SSPipe library 的解决方案。

请注意,此处使用的所有函数如 mapfilterstrlenenumeratestr.formatstr.join 除了 ppx 内置 python 函数 并且您是否不需要了解新的函数名称和 API.您唯一需要的是 p 包装器和 px 占位符:

from sspipe import p, px
a = (
    range(1, 50+1)
    | p(map, px * 4)
    | p(filter, px <= 170)
    | p(filter, p(str) | p(len) | (px == 2))
    | p(filter, px % 20 == 0)
    | p(enumerate)
    | p(map, p('Result[{0[0]}]={0[1]}'.format)) 
    | p('  ..  '.join)
)
print(a)

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

from pyxtension.streams import stream
a = stream(range(1, 50)) \
    .map(lambda _: _ * 4) \
    .filter(lambda _: _ <= 170) \
    .filter(lambda _: len(str(_)) == 2) \
    .filter(lambda _: _ % 20 == 0) \
    .enumerate() \
    .map(lambda n_x: f"Result[{n_x[0]}]={n_x[1]}") \
    .mkString("  .. ")
>  a: 'Result[0]=20  .. Result[1]=40  .. Result[2]=60  .. Result[3]=80'

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