我在 Python 中尝试了以下代码片段
l1 = [0,1]
l2 = [0,1]
a, b = 0, 1
(l1[a], l1[b], l2[l1[a]], l2[l1[b]]) = (l1[b], l1[a], a, b)
print (l1)
print (l2)
结果是:
[1, 0]
[1, 0]
然而这是我所期望的: 首先,a,b 插入整个表达式为
(l1[0], l1[1], l2[0], l2[1]) = (l1[1], l1[0], 0, 1)
最后它会打印:
[1, 0]
[0, 1]
在 Rust 上也一样,
fn main() {
let mut l1 = [0,1];
let mut l2 = [0,1];
let (a, b) = (0, 1);
(l1[a], l1[b], l2[l1[a]], l2[l1[b]]) = (l1[b], l1[a], a, b);
println!("{:?}", l1);
println!("{:?}", l2);
}
版画
[1, 0]
[1, 0]
我对这种行为的猜测是: 只评估正确的表达式
(l1[a], l1[b], l2[l1[a]], l2[l1[b]]) = (1, 0, 0, 1)
然后作业依次完成:
l1[a] = 1
l1[b] = 0
l2[l1[a]] = 0 #l2[1] = 0
l2[l1[b]] = 1 #l2[0] = 1
为什么会这样?
为什么会这样?
双方都会先全面评估RHS,然后
就 Python 而言,多重赋值(或“元组拆包”)是一个过程性事务:
[...] 对象必须是可迭代的,其项目数与目标列表中的目标数相同,并且项目从左到右分配给相应的目标。
[...]
如果目标是订阅:将评估引用中的主要表达式。它应该产生一个可变序列对象(例如列表)或映射对象(例如字典)。接下来,评估下标表达式。
请注意,此评估是在每个目标的基础上完成的,因此目标为
(l1[a], l1[b], l2[l1[a]], l2[l1[b]])
作业基本上会脱糖到:
_values = (l1[b], l1[a], a, b)
l1[a] = next(_values)
l1[b] = next(values)
l2[l1[a]] = next(values)
l2[l1[b]] = next(values)
除非你看一下反汇编,否则 Python 会更有效、更热切地做到这一点,因为它使用了
UNPACK_SEQUENCE
而不是一次推进可迭代的。
Rust 没有明确定义 解构赋值 的求值顺序,但是你可以编译成 HIR 看看它到底脱糖成什么:
fn main() {
let mut l1 = [0, 1];
let mut l2 = [0, 1];
let (a, b) = (0, 1);
{
let (lhs, lhs, lhs, lhs) = (l1[b], l1[a], a, b);
l1[a] = lhs;
l1[b] = lhs;
l2[l1[a]] = lhs;
l2[l1[b]] = lhs;
};
结果和Python差不多,为什么没有借用错误。虽然因为(再次)我找不到任何关于 assignee expression 评估顺序的规范我不确定依赖这种行为是安全的(不是说你应该在 Python 中利用它,它使得非常难以阅读代码)。