16

Is it possible to have something like:

list1 = ...

currentValue = 0
list2 = [currentValue += i, i for i in list1]

I tried that but didn't work? What's the proper syntax to write those?

EDIT: the print statement was an example. Actually I am incrementing a value outside the loop.

hlovdal
  • 26,565
  • 10
  • 94
  • 165
Joan Venge
  • 315,713
  • 212
  • 479
  • 689
  • What's wrong with the ordinary for-statement? Why bother with this when you have a perfectly good statement? What are you trying to achieve? – S.Lott Apr 22 '09 at 10:48
  • 3
    Nothing, just want to see if it's possible with list compherensions in Python. – Joan Venge Apr 22 '09 at 16:46

9 Answers9

33

Statements cannot go inside of expressions in Python; it was a complication that was deliberately designed out of the language. For this problem, try using a complication that did make it into the language: generators. Watch:

def total_and_item(sequence):
    total = 0
    for i in sequence:
        total += i
        yield (total, i)

list2 = list(total_and_item(list1))

The generator keeps a running tally of the items seen so far, and prefixes it to each item, just like it looks like you example tries to do. Of course, a straightforward loop might be even simpler, that creates an empty list at the top and just calls append() a lot! :-)

Brandon Rhodes
  • 83,755
  • 16
  • 106
  • 147
  • +1 for mentioning that statements are excluded from expressions (let alone _multiple_ statements :-), plus the recommendation of more explicit approaches. – Jarret Hardie Apr 21 '09 at 23:20
4

I'm not quite sure what you're trying to do but it's probably something like

list2 = [(i, i*2, i) for i in list1]
print list2

The statement in the list comprehension has to be a single statement, but you could always make it a function call:

def foo(i):
    print i
    print i * 2
    return i
list2 = [foo(i) for i in list1]
David Z
  • 128,184
  • 27
  • 255
  • 279
3

As pjz said, you can use functions, so here you can use a closure to keep track of the counter value:

# defines a closure to enclose the sum variable
def make_counter(init_value=0):
    sum = [init_value]
    def inc(x=0):
        sum[0] += x
        return sum[0]
    return inc

Then you do what you want with list1:

list1 = range(5)  # list1 = [0, 1, 2, 3, 4]

And now with just two lines, we get list2:

counter = make_counter(10)  # counter with initial value of 10
list2 = reduce(operator.add, ([counter(x), x] for x in list1))

In the end, list2 contains:

[10, 0, 11, 1, 13, 2, 16, 3, 20, 4]

which is what you wanted, and you can get the value of the counter after the loop with one call:

counter()  # value is 20

Finally, you can replace the closure stuff by any kind of operation you want, here we have an increment, but it's really up to you. Note also that we use a reduce to flatten list2, and this little trick requires you to import operator before calling the line with the reduce:

import operator
Manu
  • 191
  • 1
  • 8
2

Here's an example from another question:

[i for i,x in enumerate(testlist) if x == 1]

the enumerate generator returns a 2-tuple which goes into i,x.

Community
  • 1
  • 1
Charlie Martin
  • 110,348
  • 25
  • 193
  • 263
1

First of all, you likely don't want to use print. It doesn't return anything, so use a conventional for loop if you just want to print out stuff. What you are looking for is:

>>> list1 = (1,2,3,4)
>>> list2 = [(i, i*2) for i in list1] # Notice the braces around both items
>>> print(list2)
[(1, 2), (2, 4), (3, 6), (4, 8)]
phihag
  • 278,196
  • 72
  • 453
  • 469
1

Print is a weird thing to call in a list comprehension. It'd help if you showed us what output you want, not just the code that doesn't work.

Here are two guesses for you. Either way, the important point is that the value statement in a list comprehension has to be a single value. You can't insert multiple items all at once. (If that's what you're trying to do, skip to the 2nd example.)

list1 = [1, 2, 3]
list2 = [(i, i*2, i) for i in list1]
# list2 = [(1, 2, 1), (2, 4, 2), (3, 6, 3)]

To get a flat list:

list1 = [1, 2, 3]
tmp = [(i, i*2) for i in list1]
list2 = []
map(list2.extend, tmp)
# list2 = [1, 2, 1, 2, 4, 2, 3, 6, 3]

Edit: Incrementing a value in the middle of the list comprehension is still weird. If you really need to do it, you're better off just writing a regular for loop, and appending values as you go. In Python, cleverness like that is almost always branded as "unpythonic." Do it if you must, but you will get no end of flak in forums like this. ;)

ojrac
  • 13,231
  • 6
  • 37
  • 39
  • Thanks basically I need to increment a value outside the list comprehension. – Joan Venge Apr 21 '09 at 22:26
  • Can you write what you're doing as a "for item in list1" loop? It'll be easier to figure out whether it's a good fit for a list comprehension that way. – ojrac Apr 21 '09 at 22:30
1

For your edited example:

currentValue += sum(list1)

or:

for x in list1:
    currentValue += x

List comprehensions are great, I love them, but there's nothing wrong with the humble for loop and you shouldn't be afraid to use it :-)

EDIT: "But what if I wanna increment different than the collected values?"

Well, what do you want to increment by? You have the entire power of python at your command!

Increment by x-squared?

for x in list1:
    currentValue += x**2

Increment by some function of x and its position in the list?

for i, x in enumerate(list1):
    currentValue += i*x
John Fouhy
  • 41,203
  • 19
  • 62
  • 77
1

Why would you create a duplicate list. It seems like all that list comprehension would do is just sum the contents.

Why not just.

list2 = list(list1)   #this makes a copy
currentValue = sum(list2)
Matt
  • 1,841
  • 2
  • 25
  • 30
0

You can't do multiple statements, but you can do a function call. To do what you seem to want above, you could do:

list1 = ...
list2 = [ (sum(list1[:i], i) for i in list1 ]

in general, since list comprehensions are part of the 'functional' part of python, you're restricted to... functions. Other folks have suggested that you could write your own functions if necessary, and that's also a valid suggestion.

pjz
  • 41,842
  • 6
  • 48
  • 60
  • Won't this generate n lists, where n is the length of the first list? The result, if the list were thousands of elements, could take far too much time. – Brandon Rhodes Apr 21 '09 at 22:46
  • Sure, but efficiency wasn't the goal - using a list comprehension was. Your generator solution is obviously much more efficient. – pjz Apr 22 '09 at 15:40