views:

978

answers:

11

This problem has been getting at me for a while now. Is there an easier way to write nested for loops in python? For example if my code went something like this:

  for y in range(3):
    for x in range(3):
      do_something()
      for y1 in range(3):
        for x1 in range(3):
          do_something_else()

would there be an easier way to do this? I know that this code works but when you indent instead of using 2 spaces, like me, it can get to be a problem.

Oh in the example there were only 4 nested for loops to make things easier.

+4  A: 

When faced with that sort of program logic, I would probably break up the sequence of loops into two or more separate functions.

Another technique in Python is to use list comprehensions where possible, instead of a loop.

Greg Hewgill
+2  A: 

My personal argument would be that you're likely doing something wrong if you have 6 nested loops...

That said, functional decomposition is what you're looking for. Refactor so some of the loops happen in seperate function calls, then call those functions.

Matthew Scharley
Here's the use-case where I last had that many nested loops: Imagine applying a 3D gaussian "blur" filter to a 3D dataset; you've got summations in all three directions for the filter (3 loops), and iterating over every point in three directions to apply it (3 more loops). Oh, and the data I was filtering was velocities, so there were three components to iterate over (1 final loop).
Brooks Moses
+2  A: 

Assuming each loop has some sort of independent meaning, break them out into named functions:

def do_tigers():
    for x in range(3):
        print something

def do_lions():
    do_lionesses()
    for x in range(3):
        do_tigers()

def do_penguins():
    for x in range(3):
        do_lions()

..etc.

I could perhaps have chosen better names. 8-)

RichieHindle
Aww, no kittens :(
Matthew Scharley
I would do the lionesses if I were a lion.
Skurmedel
Well, that's one way of getting kittens... Though, slightly larger ones than I had in mind.
Matthew Scharley
A: 

Have you looked into List Comprehensions?

Something like:

[do_something() for x in range(3) for y in range(3)]
Yancy
A: 

That way looks pretty straightforward and easy. Are you are saying you want to generalize to multiple layers of loops.... can you give a real-life example?

Another option I could think of would be to use a function to generate the parameters and then just apply them in a loop

def generate_params(n):
    return itertools.product(range(n), range(n))

for x,y in generate_params(3):
    do_something()
Joe Koberg
A: 

you can also use the map() function

jle
+14  A: 

If you're frequently iterating over a Cartesian product like in your example, you might want to investigate Python 2.6's itertools.product -- or write your own if you're in an earlier Python.

from itertools import product
for y, x in product(range(3), repeat=2):
  do_something()
  for y1, x1 in product(range(3), repeat=2):
    do_something_else()
chrispy
Wow this answer is perfect. I'll look into using itertools.product
saleh
+2  A: 

Technically, you could use itertools.product to get a cartesian product of N sequences, and iterate over that:

 for y, x, y1, x1 in itertools.product(range(3), repeat=4):
   do_something_else()

But I don't think that actually wins you anything readability-wise.

Pavel Minaev
+3  A: 

This is fairly common when looping over multidimensional spaces. My solution is:

xy_grid = [(x, y) for x in range(3) for y in range(3)]

for x, y in xy_grid:
    # do something
    for x1, y1 in xy_grid:
        # do something else
tom10
A: 

From your code it looks like you want to perform an operation with every possible pair of points where x and y are in the range 0..2.

To do that:

for x1,y1,x2,y2 in itertools.product(range(3), repeat=4):
    do_something_with_two_points(x1,y1,2,y2)

The operation do_something_with_two_points will be called 81 times - once for every possible combination of points.

Triptych
+2  A: 

Python iterators, and generators in particular, exist exactly to allow the nice refactoring of otherwise-complicated loops. Of course, it's hard to get an abstraction out from a simple example, but assuming the 3 needs to be a parameter (maybe the whole range(3) should be?), and the two functions you're calling need some parameters that are loop variables, you could refactor the code:

  for y in range(3):
    for x in range(3):
      do_something(x, y)
      for y1 in range(3):
        for x1 in range(3):
          do_something_else(x, y, x1, y1)

into, e.g.:

def nestloop(n, *funcs):
  head = funcs[0]
  tail = funcs[1:]
  for y in range(n):
    for x in range(n):
      yield head, x, y
      if tail:
        for subtup in nestloop(n, *tail):
           yield subtup[:1] + (x, y) + subtup[1:]

for funcandargs in nestloop(3, do_something, do_something_else):
  funcandargs[0](*funcandargs[1:])

The exact kind of refactoring will no doubt need to be tweaked for your exact purposes, but the general point that iterators (and usually in fact just simple generators) afford very nice refactorings of loops remains -- all the looping logic goes inside the generator, and the application-level code is left with simple for loops and actual application-relevant processing of the items yielded in the for loops.

Alex Martelli