Python 2 list comprehension and eval

600 Views Asked by At

How do you have a multiple line statement in either a list comprehension or eval?

I was trying to turn this code:

def f(x, y, b=''):
    for i in x:
        if i in y:
            y.remove(i)
            i *= 2
        b += i
    return b

Into a lambda function like so:

j=lambda x,y:''.join(eval('y.remove(i);i*2')if i in y else i for i in x)

In both x is a string such as 'onomatopoeia' and y is a list such as ['o','a','o'].

But for some reason, it returns a syntax error. Can anyone explain this?

3

There are 3 best solutions below

0
On BEST ANSWER

First, you probably shouldn't rewrite this with a lambda because of the side-effects in the loop. If you really want to anyway, don't use an eval.

I'd suggest:

j = lambda x, y: ''.join((y.remove(i) or 2 * i) if i in y else i for i in x)

Because the result of remove is None the second argument to or will be the result. This avoids the eval. But it's still worse then a for-loop.


As noted in comments on the original question, 2 * y.pop(y.index(i)) is more readable than the or construct. You'll loop twice over y, but performance doesn't seem the issue.

0
On

If you want to write good functional expressions (using lambdas, map, reduce, filter and so on), you should avoid side effects.

I would strongly prefer your code as a function rather than a lambda with side effects.

This is a sideeffect-free implementation in one lambda expression:

>>> from functools import reduce
>>> (lambda x, y: reduce(lambda a, b: \
...     (a[0]+2*b, a[1][:a[1].index(b)]+a[1][a[1].index(b)+1:]) if b in a[1] \
...     else (a[0]+b, a[1]), x, ('',y))[0])('onomatopoeia', ['o','a','o'])
'oonoomaatopoeia'

I'm afraid it's not short nor beautiful nor easy to understand * as one would want it to be for a lambda. :/ (hopefully someone can suggest an improvement)

Just a counter-example to discourage the use of a lambda in this context.

IMHO the biggest problem with lambdas in python is that there is no where syntax like in Standard ML to define variable aliases in the same expression. So, things get ugly pretty quick for anything non trivial.


If you are interested in understanding what it does, the idea is to use reduce to run an automata, where the result (at each step) is the "state" of the computation.

The initial "state" is ('', ['o','a','o']), and the reduce function will do the replacement as needed, starting from 'onomatopoeia'.

This is the evolution of the "state":

( '',     ['o','a','o'] )    'o'
( 'oo',       ['a','o'] )    'n'
( 'oon',      ['a','o'] )    'o'
( 'oonoo',        ['a'] )    'm'
( 'oonoom',       ['a'] )    'a'
( 'oonoomaa',        [] )    't'
( 'oonoomaat',       [] )    'o'
( 'oonoomaato',      [] )    'p'
( 'oonoomaatop',     [] )    'o'
( 'oonoomaatopo',    [] )    'e'
( 'oonoomaatopoe',   [] )    'i'
( 'oonoomaatopoei',  [] )    'a'
( 'oonoomaatopoeia', [] )

and we take only first element of the last state.

4
On

I would vastly prefer your function but this will do what you want.

from itertools import chain
j = lambda x, y: ''.join(filter(None,chain.from_iterable((i * 2,y.remove(i)) if i in y else i for i in x)))

print(j("'onomatopoeia'",['o','a','o']))
'oonoomaatopoeia'