即使在理解范围之后,列表理解也会重新绑定名称。这是对的吗?

发布于 2024-10-03 00:33:09 字数 315 浏览 1 评论 0原文

推导式显示出与范围界定的不寻常交互。这是预期的行为吗?

x = "original value"
squares = [x**2 for x in range(5)]
print(x)  # Prints 4 in Python 2!

冒着抱怨的风险,这是一个残酷的错误来源。当我编写新代码时,我偶尔会发现由于重新绑定而导致的非常奇怪的错误 - 即使现在我知道这是一个问题。我需要制定一条规则,例如“始终在列表推导式中使用下划线作为临时变量的前缀”,但即使这样也不是万无一失的。 事实上,这种随机定时炸弹等待的事实否定了列表理解的所有良好的“易用性”。

Comprehensions show unusual interactions with scoping. Is this the expected behavior?

x = "original value"
squares = [x**2 for x in range(5)]
print(x)  # Prints 4 in Python 2!

At the risk of whining, this is a brutal source of errors. As I write new code, I just occasionally find very weird errors due to rebinding -- even now that I know it's a problem. I need to make a rule like "always preface temp vars in list comprehensions with underscore", but even that's not foolproof.
The fact that there's this random time-bomb waiting kind of negates all the nice "ease of use" of list comprehensions.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

网名女生简单气质 2024-10-10 00:33:09

列表推导式会在 Python 2 中泄漏循环控制变量,但在 Python 3 中不会泄漏。这是 Guido van Rossum(Python 的创建者)解释这背后的历史:

我们还在 Python 中进行了另一项更改
3、提高列表之间的等价性
理解式和生成器
表达式。在 Python 2 中,列表
理解“泄漏”循环控制
变量进入周围范围:

<前><代码>x = '之前'
a = [x 代表 1, 2, 3 中的 x]
print x # 这会打印“3”,而不是“之前”

这是原作的神器
列表理解的实现;
它是 Python 的“肮脏的小东西”之一
多年来的“秘密”。一开始是
有意妥协以列入名单
理解速度快得令人眼花缭乱,并且
虽然这不是一个常见的陷阱
初学者,绝对会刺痛人
偶尔。发电机用
表达式我们不能这样做。
生成器表达式已实现
使用生成器,其执行
需要一个单独的执行框架。
因此,生成器表达式
(特别是如果他们迭代
短序列)效率较低
比列表理解。

但是,在 Python 3 中,我们决定
修复列表的“肮脏的小秘密”
使用相同的理解
实施策略
生成器表达式。因此,在Python中
3、上面的例子(之后
修改为使用 print(x) :-) 将
打印“之前”,证明“x”
暂时在列表理解中
阴影但不覆盖“x”
周围范围内。

List comprehensions leak the loop control variable in Python 2 but not in Python 3. Here's Guido van Rossum (creator of Python) explaining the history behind this:

We also made another change in Python
3, to improve equivalence between list
comprehensions and generator
expressions. In Python 2, the list
comprehension "leaks" the loop control
variable into the surrounding scope:

x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'

This was an artifact of the original
implementation of list comprehensions;
it was one of Python's "dirty little
secrets" for years. It started out as
an intentional compromise to make list
comprehensions blindingly fast, and
while it was not a common pitfall for
beginners, it definitely stung people
occasionally. For generator
expressions we could not do this.
Generator expressions are implemented
using generators, whose execution
requires a separate execution frame.
Thus, generator expressions
(especially if they iterate over a
short sequence) were less efficient
than list comprehensions.

However, in Python 3, we decided to
fix the "dirty little secret" of list
comprehensions by using the same
implementation strategy as for
generator expressions. Thus, in Python
3, the above example (after
modification to use print(x) :-) will
print 'before', proving that the 'x'
in the list comprehension temporarily
shadows but does not override the 'x'
in the surrounding scope.

定格我的天空 2024-10-10 00:33:09

是的,列表推导式在 Python 2.x 中“泄漏”了它们的变量,就像 for 循环一样。

回想起来,这被认为是一个错误,并且通过生成器表达式避免了它。编辑:作为 马特B. 注意到当集合和字典理解语法从 Python 3 向后移植时,也可以避免这种情况。

列表理解的行为必须保留在 Python 2 中,但它在 Python 3 中已完全修复。

这意味着在all of:

list(x for x in a if x>32)
set(x//4 for x in a if x>32)         # just another generator exp.
dict((x, x//16) for x in a if x>32)  # yet another generator exp.
{x//4 for x in a if x>32}            # 2.7+ syntax
{x: x//16 for x in a if x>32}        # 2.7+ syntax

x 始终是表达式的本地变量,而这些:

[x for x in a if x>32]
set([x//4 for x in a if x>32])         # just another list comp.
dict([(x, x//16) for x in a if x>32])  # yet another list comp.

在 Python 2.x 中 all 将 x 变量泄漏到周围范围。


Python 3.8 更新PEP 572< /a> 引入了 := 赋值运算符,它故意泄漏出推导式和生成器表达式!这种泄漏本质上是由 2 个用例引发的:从 any()all() 等早期终止函数捕获“见证”:

if any((comment := line).startswith('#') for line in lines):
    print("First comment:", comment)
else:
    print("There are no comments")

以及更新可变状态:

total = 0
partial_sums = [total := total + v for v in values]

请参阅< a href="https://www.python.org/dev/peps/pep-0572/#appendix-b-rough-code-translations-for-compressivens" rel="nofollow noreferrer">附录 B以获得准确的范围。该变量会在最接近的周围 deflambda 中分配,除非该函数将其声明为 nonlocalglobal

Yes, list comprehensions "leak" their variable in Python 2.x, just like for loops.

In retrospect, this was recognized to be a mistake, and it was avoided with generator expressions. EDIT: As Matt B. notes it was also avoided when set and dictionary comprehension syntaxes were backported from Python 3.

List comprehensions' behavior had to be left as it is in Python 2, but it's fully fixed in Python 3.

This means that in all of:

list(x for x in a if x>32)
set(x//4 for x in a if x>32)         # just another generator exp.
dict((x, x//16) for x in a if x>32)  # yet another generator exp.
{x//4 for x in a if x>32}            # 2.7+ syntax
{x: x//16 for x in a if x>32}        # 2.7+ syntax

the x is always local to the expression while these:

[x for x in a if x>32]
set([x//4 for x in a if x>32])         # just another list comp.
dict([(x, x//16) for x in a if x>32])  # yet another list comp.

in Python 2.x all leak the x variable to the surrounding scope.


UPDATE for Python 3.8: PEP 572 introduced := assignment operator that deliberately leaks out of comprehensions and generator expressions! This leaking was motivated by essentially 2 use cases: capturing a "witness" from early-terminating functions like any() and all():

if any((comment := line).startswith('#') for line in lines):
    print("First comment:", comment)
else:
    print("There are no comments")

and updating mutable state:

total = 0
partial_sums = [total := total + v for v in values]

See Appendix B for exact scoping. The variable is assigned in closest surrounding def or lambda, unless that function declares it nonlocal or global.

痞味浪人 2024-10-10 00:33:09

是的,赋值发生在那里,就像在 for 循环中一样。没有创建新的范围。

这绝对是预期的行为:在每个周期中,该值都绑定到您指定的名称。例如,

>>> x=0
>>> a=[1,54,4,2,32,234,5234,]
>>> [x for x in a if x>32]
[54, 234, 5234]
>>> x
5234

一旦认识到这一点,似乎很容易避免:不要在推导式中使用现有的变量名称。

Yes, assignment occurs there, just like it would in a for loop. No new scope is being created.

This is definitely the expected behavior: on each cycle, the value is bound to the name you specify. For instance,

>>> x=0
>>> a=[1,54,4,2,32,234,5234,]
>>> [x for x in a if x>32]
[54, 234, 5234]
>>> x
5234

Once that's recognized, it seems easy enough to avoid: don't use existing names for the variables within comprehensions.

时光清浅 2024-10-10 00:33:09

有趣的是,这不会影响字典或集合推导式。

>>> [x for x in range(1, 10)]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x
9
>>> {x for x in range(1, 5)}
set([1, 2, 3, 4])
>>> x
9
>>> {x:x for x in range(1, 100)}
{1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99}
>>> x
9

然而,如上所述,它已在 3 中修复。

Interestingly this doesn't affect dictionary or set comprehensions.

>>> [x for x in range(1, 10)]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x
9
>>> {x for x in range(1, 5)}
set([1, 2, 3, 4])
>>> x
9
>>> {x:x for x in range(1, 100)}
{1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99}
>>> x
9

However it has been fixed in 3 as noted above.

寻梦旅人 2024-10-10 00:33:09

对于 python 2.6,当这种行为不合需要时,可以使用一些解决方法

# python
Python 2.6.6 (r266:84292, Aug  9 2016, 06:11:56)
Type "help", "copyright", "credits" or "license" for more information.
>>> x=0
>>> a=list(x for x in xrange(9))
>>> x
0
>>> a=[x for x in xrange(9)]
>>> x
8

some workaround, for python 2.6, when this behaviour is not desirable

# python
Python 2.6.6 (r266:84292, Aug  9 2016, 06:11:56)
Type "help", "copyright", "credits" or "license" for more information.
>>> x=0
>>> a=list(x for x in xrange(9))
>>> x
0
>>> a=[x for x in xrange(9)]
>>> x
8
-小熊_ 2024-10-10 00:33:09

在 python3 中,在列表理解中,变量在作用域结束后不会发生变化,但是当我们使用简单的 for 循环时,变量会被重新分配到作用域之外。

我 = 1
打印(一)
print([i 在范围 (5) 内])
打印(一)
i 的值将仅保持为 1。

现在只需使用 for 循环,i 的值就会被重新分配。

In python3 while in list comprehension the variable is not getting change after it's scope over but when we use simple for-loop the variable is getting reassigned out of scope.

i = 1
print(i)
print([i in range(5)])
print(i)
Value of i will remain 1 only.

Now just use simply for loop the value of i will be reassigned.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文