为什么Python要求列表推导中的for子句顺序相反?

3

看这两个列表推导式,都包含了两个for子句。我们发现,如果for子句的顺序正确,Python会混淆。但如果它们的顺序颠倒了,Python可以处理。为什么呢?

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]
>>>

我可以看到这是Python所要求的。我想问的是为什么会是这样。 - Hammerite
3
我避免使用嵌套的列表推导式,正是因为这个原因... - mgilson
在我看來,在列表理解中使用多個子句是一种不好的形式……这将使得代码难以阅读和维护… - Joran Beasley
delnan,我认为这是错误的方式,因为在单个for子句的情况下,列表推导式是通过将for子句放在推导式主体之后构建的。如果您迭代此构造,则自然会使最外层循环最靠右边。 - Hammerite
1
@Hammerite 我并不是说你喜欢的顺序没有理由。但是,也有理由采用另一种方式(已经多次给出了一个原因:它与等效的 for: 循环的顺序相匹配)。我对“错误”和“正确”的问题感到困扰。 - user395760
显示剩余9条评论
3个回答

5

In

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

range(y)中的y在此处是未知的。等同于:

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

更多信息请参见PEP-0202

- The form [... for x... for y...] nests, with the last index
      varying fastest, just like nested for loops.

我可以看到那是事实。我的问题是为什么会这样。由于我们将for循环子句附加到右侧,因此“外部”范围(y)在右侧更有意义。 - Hammerite
使用嵌套的for循环比多个for LC更清晰易懂。 - Joran Beasley
除非我错过了什么,否则你的链接再次解释了“是什么”,但没有解释“为什么”。 - Hammerite

3

这是一个非常好的问题!

答案是这样的,与Python的大多数不同,这些嵌套的推导式是按照解释器想要读取它们的方式编写的,而不是你想要编写它们的方式。

解释器从右到左读取推导式:

[(this is interpreted last) for (this is interpreted first)]

然而,每个子句从左到右读取,在y in range(4) for x in range(y)中,您必须先解释y是什么,然后才能从中range(y)

这很令人困惑,因为当您考虑推导的嵌套时,您会认为是从右到左。


2
“从左到右”这种说法并不准确。毕竟,在正确的用法中,即使在for y in range(4)定义之前,y也会出现在x + y中。 - David Robinson
是的,解释器从右到左评估理解 - 就像许多其他语言一样。 - Piotr Hajduga
@DavidRobinson -- 你说得对,但这已经是你能得到的最接近“为什么”问题的答案了。其他的回答/评论在我看来可能就像是“因为它就是这样”的说辞一样。 - mgilson
解释器只会从右到左解释理解。 - martineau

3
列表推导式最初是作为一种语法糖引入到Python中的,用于简化下面这种形式:
L = []
for innerseq in seq:
    for item in innerseq:
        LOOPS
            if CONDITION:
                L.append(BODY)

这将被转换为:
[BODY for innerseq in seq for item in innerseq LOOPS if CONDITION]

为了使转换更加明显,请注意for表达式和if条件出现的顺序与普通for循环完全相同。这就是列表推导式使用相同顺序的原因。
当您将循环重写为推导式时,唯一改变的是循环体的位置(它移到了前面,您通常会在那里初始化空容器)。循环的其他方面保持完全不变。
您偏好的替代方法("正确的方式")似乎都更加令人困惑。要么我们只是颠倒循环的顺序,要么我们反转推导式中每个从句的顺序。即,要么:
[BODY LOOPS[::-1] for item in innerseq for innerseq in seq if CONDITION]

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

这两种方法都似乎是一个不必要的复杂转换。

另外请注意,其他语言在列表推导中使用相同的循环顺序。以下是一些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)

这与Python相同,即使Clojure将推导式的“主体”放在末尾。
如果有帮助,请想象for循环像汽车里程表中的数字一样排列。最右边的循环旋转得最快。

评估顺序相同 - 从左到右。我可以更改Clojure示例以使用定义变量,但这不会改变所展示的原则。 - Francis Avila
你建议反转循环的顺序或每个子句的顺序 - 我认为应该反转for子句的顺序,每个条件都放置在最外层有意义的for子句的右侧。这与数学中的集合构建符号相似:我们写成{P(x) for x in X such that Q(x)}; 我们可以写成{P(x, y) for x in X such that Q(x, y) for y in Y such that R(y)}。 - Hammerite
Python没有数学血统。在大多数编程语言中,变量按照从左到右的顺序在代码读取时进行评估或定义。在语言中具有特定的特殊情况,其中变量的存在顺序与它们被读取的顺序相反,这将是不明智的,特别是对于像Python这样强调可读性和最小惊喜的语言。 - Francis Avila
还可以想象一下,你正在将一个普通的循环重构为列表推导式,或者反过来。如果循环项被反转,这个转换就会变得非常复杂(重新排列子句),而不是只需将主体复制粘贴到新位置并添加一些缩进。 - Francis Avila
顺便提一下,我已经更改了Clojure示例,以展示xy变量的相互依赖性和创建顺序,这是您原始示例中的情况。 - Francis Avila
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接