38

Possible Duplicate:
What is the most “pythonic” way to iterate over a list in chunks?

I am reading in some PNG data, which has 4 channels per pixel. I would like to iterate over the data 1 pixel at a time (meaning every 4 elements = 1 pixel, rgba).

red_channel = 0
while red_channel < len(raw_png_data):
    green_channel, blue_channel, alpha_channel = red_channel +1, red_channel +2, red_channel +3
    # do something with my 4 channels of pixel data ... raw_png_data[red_channel] etc
    red_channel += 4

This way doesnt really seem "right". Is there a more Pythonic way to iterate over a sequence, 4 items at a time, and have those 4 items unpacked?

Community
  • 1
  • 1
blasted
  • 381
  • 1
  • 3
  • 3

5 Answers5

40

(Python's itertools should really make all recipes as standard functions...)

You could use the grouper function:

from itertools import zip_longest
def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

Then you can iterate the pixels by

for r,g,b,a in grouper(4, raw_png_data):
  ....

Alternatively, you could use

irpd = iter(raw_png_data)
for r,g,b,a in zip(irpd, irpd, irpd, irpd):  # use itertools.izip in Python 2.x
  ....

Note that this will chop the last few bytes if the iterable's length is not a multiple of 4. OTOH, the grouper function uses izip_longest, so the extra bytes will be padded with None for that.

vy32
  • 28,461
  • 37
  • 122
  • 246
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
35
vars = [1, 2, 3, 4, 5, 6, 7, 8]
for a, b, c, d in zip(*[iter(vars)]*4):
    print a, b, c, d
Tomasz Wysocki
  • 11,170
  • 6
  • 47
  • 62
  • 1
    But note. This solutions will works only for vars list length aliquot to 4, for iterating by 4 – Eugene Nagorny Dec 27 '12 at 21:44
  • This works for me, but as @EugeneNagorny is saying, anyone using this should note that if your list has fewer items than a multiple of the "aliquot", it will omit the "remainder". For example, for ten values in a list and an aliquot of 4, it will only emit two sets of four values, ignoring the last two. – Christopher Bottoms Sep 06 '18 at 16:13
9
from itertools import izip
for r,g,b,a in izip(*[iter(data)]*4):
    ...
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
3
for r, g, b, t in (data[i:i+4] for i in xrange(0, len(data)/4*4, 4)):
    print r, g, b, t
Sjoerd
  • 74,049
  • 16
  • 131
  • 175
-4

Try something like this:

for red, green, blue, alpha in raw_png_data:
    #do something

You can pull out multiple items and never have to use an iterator. :)

Edit: This would mean that raw_png_data needs to be a list of 4 value tuples. It would be most pythonic to put each rgba group into a tuple and then append it to raw_png_data and iterate through like my example.

excid3
  • 1,658
  • 15
  • 31
  • 7
    Obviously this will not work, unless raw_png_data is a list of tuples. Right? – Sjoerd Aug 05 '10 at 13:32
  • Yeah, but it would be a good idea when raw_png_data is created, to group them into tuples since each group of 4 values is related. – excid3 Aug 05 '10 at 13:36
  • 3
    @excid3 That's what this question is asking for, how to group the raw sequence into tuples. – augurar Nov 15 '17 at 02:12