简体   繁体   English

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

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

Is there any possible way to achieve a non-lazy left to right invocation of operations on a list in Python?有什么可能的方法来实现对 Python 列表的非惰性从左到右调用操作?

Eg Scala:例如斯卡拉:

 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

While I realize many folks will not prefer the above syntax, I like the ability to move left to right and add arbitrary operations as we go.虽然我知道很多人不喜欢上面的语法,但我喜欢从左到右移动并添加任意操作的能力。

The Python for comprehension is IMO not easy to read when there are three or more operations.当有三个或更多操作时, for理解的 Python 是 IMO 不容易阅读。 The result seems to be we're required to break everything up into chunks.结果似乎是我们需要将所有内容分解成块。

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

Is there any chance for the approach mentioned?提到的方法有机会吗?

Note: I tried out a few libraries including toolz.functoolz .注意:我尝试了一些库,包括toolz.functoolz That one is complicated by Python 3 lazy evaluation: each level returns a map object. Python 3 懒惰求值使这个变得复杂:每个级别都返回一个map对象。 In addition, it is not apparent that it can operate on an input list .此外,它是否可以对输入list进行操作并不明显。

The answer from @JohanL does a nice job of seeing what the closest equivalent is in standard python libraries. @JohanL 的回答很好地了解了标准 python 库中最接近的等价物。

I ended up adapting a gist from Matt Hagy in November 2019 that is now in pypi我最终在 2019 年 11 月改编了 Matt Hagy 的要点,现在在pypi

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

Other approaches described in other answers其他答案中描述的其他方法

Older approaches较旧的方法

I found a more appealing toolkit in Fall 2018我在 2018 年秋季找到了一个更有吸引力的工具包

https://github.com/dwt/fluent https://github.com/dwt/fluent

在此处输入图像描述

After a fairly thorough review of the available third party libraries it seems the Pipe https://github.com/JulienPalard/Pipe best suits the needs.在对可用的第三方库进行相当彻底的审查后, Pipe https://github.com/JulienPalard/Pipe似乎最适合需求。

You can create your own pipeline functions.您可以创建自己的管道功能。 I put it to work for wrangling some text shown below.我用它来处理下面显示的一些文本。 the bolded line is where the work happens.粗线是工作发生的地方。 All those @Pipe stuff I only have to code once and then can re-use.所有这些@Pipe的东西我只需要编写一次代码就可以重复使用。

The task here is to associate the abbreviation in the first text:这里的任务是关联第一个文本中的缩写:

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"""

With an associated tag in this second text:在第二个文本中使用关联标签:

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

Here's the one-time coding for the functional operations (reuse in subsequent pipelines):这是功能操作的一次性编码(在后续管道中重用):

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

And here's the big finale : all in one pipeline:这是大结局:全部在一个管道中:

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

And the result:结果:

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

Here is another solution using SSPipe library .这是另一个使用SSPipe 库的解决方案。

Note that all functions used here like map , filter , str , len , enumerate , str.format , str.join except p and px are builtin python functions and are you don't need to learn about new function names and API.请注意,此处使用的所有函数,如mapfilterstrlenenumeratestr.formatstr.join除了ppx都是内置的 python 函数,您不需要了解新的函数名称和 API。 The only thing you need is the p wrapper and px placeholder:您唯一需要的是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)

Even though it is not considered Pythonic, Python still contains map and filter and reduce can be imported from functools .尽管它不被认为是 Pythonic,Python 仍然包含mapfilter ,并且可以从functools导入reduce Using these functions it is possible to generate the same pipe line as the one you have in scala, albeit it will be written in the opposite direction (from right to left, rather than left to right):使用这些函数可以生成与 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))))))))

Now, this is lazy, in the sense that it will let each value be transported through the whole pipe before the next is being evaluated.现在,这是惰性的,因为它会让每个值在评估下一个值之前通过整个管道传输。 However, since all values are consumed by the final reduce call, this is not seen.但是,由于最终的reduce调用消耗了所有值,因此看不到这一点。

It is possible to generate a list from each map or filter object in each step:可以在每个步骤中从每个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)))))))))))))))

Both of these expressions, especially the second one, are quite verbose, so I don't know if I would recommend them.这两个表达式,尤其是第二个,都相当冗长,所以我不知道我是否会推荐它们。 I would recommend using list/generator comprehensions and a few intermediate varaiables:我建议使用列表/生成器理解和一些中间变量:

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)

Another benefit with this approach (for you, at least) is that with this approach you will keep the order of opeations that is found in scala.这种方法的另一个好处(至少对您而言)是,使用这种方法您将保持在 Scala 中找到的操作顺序。 It will also, as long as we do list comprehension (as shown) be non-lazy evaluated.它也将,只要我们做列表理解(如图所示)是非惰性评估的。 If we want lazy evaluation, it is possible to do generator comprehension instead:如果我们想要惰性评估,则可以改为进行生成器推导:

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)

Thus, the only difference is that we use parantheses instead of brackets.因此,唯一的区别是我们使用括号而不是方括号。 But, as stated before;但是,如前所述; since all data is consumed, the difference in this example is rather minimal.由于所有数据都已消耗,因此此示例中的差异非常小。

There's a library that already does exactly what you are looking for, ie the fluid syntaxt, lazy evaluation and the order of operations is the same with how it's written, as well as many more other good stuff like multiprocess or multithreading Map/Reduce.有一个库已经完全满足您的需求,即流畅的语法、惰性评估和操作顺序与其编写方式相同,还有许多其他好东西,如多进程或多线程 Map/Reduce。 It's named pyxtension and it's prod ready and maintained on PyPi .它被命名为pyxtension并且它已经准备好并在PyPi上维护。 Your code would be rewritten in this form:您的代码将以这种形式重写:

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'

Replace map with mpmap for multiprocessed map, or fastmap for multithreaded map.map替换为mpmap用于多处理 map,或fastmap用于多线程 map。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM