Python列表理解甚至在理解范围之后重新命名。 这是正确的吗?
列表解析与范围界定有一些意想不到的相互作用。 这是预期的行为?
我有一个方法:
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
有呜咽的风险,这是一个残酷的错误来源。 当我编写新代码时,由于重新绑定,偶尔会发现非常奇怪的错误 – 即使现在我知道这是一个问题。 我需要制定一个规则,比如“总是在带有下划线的列表解析中引入临时变量”,但即使这样也不是没有错的。
这个随机的定时炸弹等待的事实否定了列表理解的所有好的“易用性”。
列表解析在Python 2中泄露了循环控制变量,但在Python 3中泄露了循环控制变量。下面是Guido van Rossum(Python的创建者) 解释这个背后的历史:
我们还在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):-)进行修改之后将会打印'before',证明列表理解中的'x'暂时阴影,但不会覆盖周围的'x'范围。
是的,列表解析在Python 2.x中“泄漏”它们的变量,就像for循环一样。
回想起来,这被认为是一个错误,它是用发生器表达式来避免的。 编辑:正如马特B.注意到它也避免了当设置和字典理解语法从Python 3反向移植。
列表解析的行为必须保留在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.
在Python 2.x中,都将x
变量泄漏到周围的范围。
是的,分配发生在那里,就像在for
循环中一样。 没有新的范围正在创建。
这肯定是预期的行为:在每个循环中,该值都与您指定的名称绑定。 例如,
>>> x=0 >>> a=[1,54,4,2,32,234,5234,] >>> [x for x in a if x>32] [54, 234, 5234] >>> x 5234
一旦被认可,似乎很容易避免:不要在理解中使用现有的变量名称。
有趣的是,这不会影响字典或集合理解。
>>> [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。