元组分配如何在低级别工作?

问题描述 投票:0回答:1

我在 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

为什么会这样?

python rust tuples
1个回答
0
投票

为什么会这样?

双方都会先全面评估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 中利用它,它使得非常难以阅读代码)。

© www.soinside.com 2019 - 2024. All rights reserved.