Слева направо применение операций над списком в python3


Существует ли какой-либо возможный способ добиться не-ленивого вызова слева направо операций над списком в 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 .
2 5

2 ответа:

Ответ от @JohanL делает хорошую работу, чтобы увидеть, что ближайший эквивалент находится в стандартных библиотеках python.

После довольно тщательного обзора имеющихсясторонних библиотек представляется, что Pipe https://github.com/JulienPalard/Pipe лучше всего соответствует потребностям .

Введите описание изображения здесь

Обновить следующим образом: это круто. Вы можете создать свои собственные функции конвейера. Я поставил его на работу для пререканий с каким-то текстом. под. жирная линия - это место, где происходит работа. Все эти @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'}

Несмотря на то, что он не считается Питоническим, Python все еще содержит map и filter и 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, этого не видно.

Можно генерировать список из каждого объекта 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)))))))))))))))
Оба эти выражения, особенно второе, довольно многословны, поэтому я не знаю, Рекомендую ли я их. Я бы рекомендовал использовать список / генератор понимания и несколько промежуточных 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)

Еще одно преимущество такого подхода (по крайней мере, для вас) заключается в том, что при таком подходе вы будете сохранять порядок операций, который находится в 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)
Таким образом, единственная разница заключается в том, что мы используем парантезы вместо скобок. Но, как было сказано ранее, поскольку все данные потребляются, разница в этом примере довольно минимальна.