Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Python require for clauses in list comprehensions to be the wrong way around?

Behold two list comprehensions, each involving two for clauses. We see that if the for clauses are in the correct order, Python gets confused. But if they are the wrong way around, Python can handle it. Why?

Python 3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)] on win
32
Type "help", "copyright", "credits" or "license" for more information.
>>> [x + y for x in range(y) for y in range(4)]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> [x + y for y in range(4) for x in range(y)]
[1, 2, 3, 3, 4, 5]
>>>
like image 672
Hammerite Avatar asked Oct 27 '25 15:10

Hammerite


2 Answers

In

[x + y for x in range(y) for y in range(4)]

the y in range(y) is unknown at that place. Equivalent to:

for x in range(y):
    for y in range(4):
        # x + y

See PEP-0202 for more information:

- The form [... for x... for y...] nests, with the last index
      varying fastest, just like nested for loops.
like image 66
eumiro Avatar answered Oct 29 '25 07:10

eumiro


List comprehensions were first introduced into Python as syntactic sugar for this form:

L = []
for innerseq in seq:
    for item in innerseq:
        LOOPS
            if CONDITION:
                L.append(BODY)

This is transformed to:

[BODY for innerseq in seq for item in innerseq LOOPS if CONDITION]

To make the transformation more obvious, notice that the for expressions and the if condition occur in exactly the same order as they would in a normal for-loop. This is why the list comprehension uses the same order.

When you rewrite loops as a comprehension, the only thing that changes is the placement of the body of the loop (it moves to the front, where you would normally initialize your empty container). Everything else about the loop remains exactly the same.

The alternatives you prefer (your "right way") both seem far more confusing. Either we just reverse the order of the loops, or we reverse the order of every clause in the comprehension. I.e., either:

[BODY LOOPS[::-1] for item in innerseq for innerseq in seq if CONDITION]

Or

[BODY if CONDITION LOOPS[::-1] for item in innerseq for innerseq in seq]

Either of these seem like an unnecessarily complicated transformation.

Also note that other languages use the same order for loops in their list comprehensions. Here is some Clojure:

user=> ; using your suggested "right" order
user=> (for [x (range y) y (range 4)] (+ x y))
CompilerException java.lang.RuntimeException: Unable to resolve symbol: y in this context, compiling:(NO_SOURCE_PATH:1) 
user=> ; you need to use the same "wrong" order as Python
user=> (for [y (range 4) x (range y)] (+ x y))
(1 2 3 3 4 5)

This is the same as Python even though Clojure puts the "body" of the comprehension at the end.

If it helps, imagine that the for loops are arranged like the digits in a car odometer. The right-most loop spins the fastest.

like image 26
Francis Avila Avatar answered Oct 29 '25 06:10

Francis Avila



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!