为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

慕码人2483693 2019-06-24 13:01:01
即使在理解范围之后,列表理解也会重新绑定名称。这是对的吗?理解与范围界定有一些意想不到的互动。这是预期的行为吗?我有个方法:def leave_room(self, uid):   u = self.user_by_id(uid)   r = self.rooms[u.rid]   other_uids = [ouid for ouid in r.users_by_id.keys() if ouid != u.uid]   other_us = [self.user_by_id(uid) for uid in other_uids]   r.remove_user(uid) # OOPS! uid has been re-bound by the list comprehension above   # Interestingly, it's rebound to the last uid in the list, so the error only shows   # up when len > 1冒着抱怨的危险,这是一个残酷的错误来源。当我编写新代码时,我偶尔会发现由于重新绑定而产生的非常奇怪的错误-即使现在我知道这是个问题。我需要制定一条规则,比如“总是用下划线来解释列表中的临时变量”,但即使这样也不能防止愚昧。事实上,这种随机的定时炸弹在等待某种程度上否定了列表理解的所有好的“易用性”。
查看完整描述

3 回答

?
动漫人物

TA贡献1815条经验 获得超10个赞

列表理解在Python 2中泄漏循环控制变量,但在Python 3中没有泄漏。解释这背后的历史:

我们还对Python 3进行了另一项修改,以改进列表理解和生成器表达式之间的等价性。在Python 2中,列表理解“泄漏”循环控制变量到周围的作用域中:

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

这是列表理解的原始实现的一个工件;多年来它一直是Python的“肮脏的小秘密”之一。它最初是一种有意的妥协,目的是让列表理解得快得让人目瞪口呆,虽然对于初学者来说这不是一个常见的陷阱,但它确实偶尔会刺痛人们。对于生成器表达式,我们不能这样做。生成器表达式是使用生成器实现的,生成器的执行需要一个单独的执行框架。因此,生成器表达式(特别是在短序列上迭代时)的效率低于列表理解。

然而,在Python 3中,我们决定通过使用与生成器表达式相同的实现策略来修正列表理解的“肮脏的小秘密”。因此,在Python 3中,上述示例(修改后使用print(X):-)将打印“Are”,证明列表理解中的“x”暂时阴影,但不覆盖周围范围中的“x”。


查看完整回答
反对 回复 2019-06-24
?
慕村225694

TA贡献1880条经验 获得超4个赞

是的,列表理解在Python2.x中“泄漏”它们的变量,就像for循环一样。

回想起来,这被认为是一个错误,并避免了生成器表达式。编辑:AS马特B.笔记在Python 3支持SET和字典理解语法时,也避免了这种情况。

列表理解的行为必须保留在Python 2中,但它在Python 3中是完全固定的。

这意味着,在所有这些方面:

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.

在Python2.x中,都会泄漏x变量到周围的作用域。


更新Python3.8(?)佩普572将介绍:=赋值算子故意泄漏从理解和生成器的表达!它的动机主要有两个用例:从早期终止的函数中捕获一个“见证”,比如any()all():

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

并更新可变状态:

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

看见附录B精确的范围。变量被赋值在最近的周围。deflambda,除非该函数声明nonlocalglobal.


查看完整回答
反对 回复 2019-06-24
?
冉冉说

TA贡献1877条经验 获得超1个赞

是的,任务就发生在那里,就像在for循环。没有创建新的范围。

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

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

一旦认识到这一点,就很容易避免:不要在可理解的范围内使用现有的名称来表示变量。


查看完整回答
反对 回复 2019-06-24
  • 3 回答
  • 0 关注
  • 369 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信