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

2024-05-08 13:14:48 发布

您现在位置:Python中文网/ 问答频道 /正文

有没有任何可能的方法可以在Python中实现从左到右的列表操作的非惰性调用

例如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

虽然我意识到很多人不喜欢上面的语法,但我喜欢从左向右移动的能力,并在执行过程中添加任意操作

当有三个或三个以上的操作时,Python for理解是不容易理解的。结果似乎是我们被要求把所有的东西都分成小块

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

是否有机会采用上述方法

注意:我试用了一些库,包括toolz.functoolz。Python3惰性计算使这个问题变得复杂:每个级别返回一个map对象。此外,它是否可以对输入list进行操作并不明显


Tags: to方法inmap列表forvalresult
3条回答

来自@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

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

旧方法

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

https://github.com/dwt/fluent

enter image description here

在对可用的第三方库进行了相当彻底的审查之后,它似乎最适合于需求

您可以创建自己的管道函数。我把它用于争论如下所示的一些文本。粗体线是工作发生的地方。所有这些@Pipe东西我只需要编码一次就可以重用

此处的任务是关联第一个文本中的缩写:

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"

以下是功能操作的一次性编码(在后续管道中重用):

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

即使它不被认为是Pythonic,Python仍然包含mapfilter,并且reduce可以从functools导入。使用这些函数,您可以生成与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)))))))))))))))

这两种表达方式,尤其是第二种,都非常冗长,所以我不知道我是否会推荐它们。我建议使用列表/生成器理解和一些中间变量:

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中的操作顺序。它也将,只要我们做列表理解(如图所示)是非惰性的评估。如果我们想要惰性评估,可以改为执行生成器理解:

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)

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

相关问题 更多 >