在 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 仍然包含 map
和 filter
并且 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
调用消耗了所有值,因此看不到。
可以在每个步骤中从每个 map
或 filter
对象生成一个列表:
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 年秋季发现了一个更具吸引力的工具包
在对可用的 第三方 库进行相当彻底的审查后,似乎 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 的解决方案。
请注意,此处使用的所有函数如 map
、filter
、str
、len
、enumerate
、str.format
、str.join
除了 p
和 px
是 内置 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
表示多线程映射。
有什么方法可以在 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 仍然包含 map
和 filter
并且 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
调用消耗了所有值,因此看不到。
可以在每个步骤中从每个 map
或 filter
对象生成一个列表:
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 年秋季发现了一个更具吸引力的工具包
在对可用的 第三方 库进行相当彻底的审查后,似乎 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 的解决方案。
请注意,此处使用的所有函数如 map
、filter
、str
、len
、enumerate
、str.format
、str.join
除了 p
和 px
是 内置 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
表示多线程映射。