繁体   English   中英

python a,b = b,a 实现? 它与 C++ 交换函数有何不同?

[英]python a,b = b,a implementation? How is it different from C++ swap function?

当我想尝试 python 版本时遇到了这个问题: https : //leetcode.com/problems/first-missing-positive/discuss/17071/My-short-c++-solution-O( 1)-space- and -准时

我不知道为什么a[0], a[a[0]] = a[a[0]], a[0]这个不做交换?

>>> nums
[2, 1, 0]
>>> a = [2,1,0]
>>> a[0], a[a[0]] = a[a[0]], a[0]
>>> a
[2, 1, 0]
>>> a[0]
2
>>> a[0],a[2] = a[2], a[0]
>>> a
[0, 1, 2]

我的猜测是 a, b = b, a 语法的实现是这样的:

tmp = a[0] (tmp = 2)
a[0]  = a[a[0]] (a[0] = a[2] = 0)
a[a[0]] = tmp (a[a[0]] = a[0] = tmp = 2)

然后我检查了C++中swap函数的实现。 我对 C++ 一无所知,但看起来想法是一样的: http : //www.cplusplus.com/reference/algorithm/swap/

The behavior of these function templates is equivalent to:
template <class T> void swap (T& a, T& b)
{
  T c(std::move(a)); a=std::move(b); b=std::move(c);
}
template <class T, size_t N> void swap (T (&a)[N], T (&b)[N])
{
  for (size_t i = 0; i<N; ++i) swap (a[i],b[i]);
}

我们有 c = a,然后 a = b 和 b = a 那么为什么 C++ 交换函数没有这个问题呢? 以及如何以 Pythonic 的方式编写这种交换函数?

这种行为确实与Python对类型表达式求值的方式有关

a,b=b,a

实际上,Python 所做的首先是通过创建元组(b,a)来“准备”右侧的值。 然后这个元组被解包并以相反的顺序分配给变量。

需要注意的是,尽管 Python 使用对对象的引用,但如果变量名称引用对象引用不可变类型的值,则它们可能会更改 可变类型并非如此( 在 Python FAQ 中以示例说明)。

要使用您使用的可变类型(列表)分解示例:

a = [2,1,0]    
a[0], a[a[0]] = a[a[0]], a[0]
  1. a[a[0]]从列表a (值0 )的a[0]元素(等于2 )中获取值。
  2. a[0]2因此创建的元组是(0,2)
  3. 元组(0,2)被解包, 0替换列表中的2 (第 0 个元素)。
  4. 现在, a[a[0]]可以理解为:以列表的第0个元素a (这是目前0 ),然后在该位置与替换该列表中的值2从元组拆包(现在0被替换为2 -这使操作看起来对列表没有任何作用)。

正如von Oak的回答中所建议的那样更改顺序会有所帮助,因为上面第 4 点的步骤不会再次替换该值。

我建议您参考通过赋值的答案来理解函数和参数传递。

要理解这一点,您需要使用dis深入实现。

首先让我们考虑一个简单的交换函数

from dis import dis

def swap(i, j):
    i, j = j, i

dis(swap)

输出字节码:

4             0 LOAD_FAST                1 (j)
              2 LOAD_FAST                0 (i)
              4 ROT_TWO
              6 STORE_FAST               0 (i)
              8 STORE_FAST               1 (j)
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE

你可以看到有ROT_TWO这意味着

交换最顶部的两个堆栈项。

这个ROT_TWO主要负责交换。

现在来回答你的问题:

让我们以正在运行示例为例:

from dis import dis

def swap():
    a = [2, 1]
    a[0], a[1] = a[1], a[0]

dis(swap)

输出字节码

  4           0 LOAD_CONST               1 (2)
              2 LOAD_CONST               2 (1)
              4 BUILD_LIST               2
              6 STORE_FAST               0 (a)

  5           8 LOAD_FAST                0 (a)
             10 LOAD_CONST               2 (1)
             12 BINARY_SUBSCR
             14 LOAD_FAST                0 (a)
             16 LOAD_CONST               3 (0)
             18 BINARY_SUBSCR
             20 ROT_TWO
             22 LOAD_FAST                0 (a)
             24 LOAD_CONST               3 (0)
             26 STORE_SUBSCR
             28 LOAD_FAST                0 (a)
             30 LOAD_CONST               2 (1)
             32 STORE_SUBSCR
             34 LOAD_CONST               0 (None)
             36 RETURN_VALUE

输出字节码类似于我们在它是一个简单的交换函数时所拥有的。

但是当代码改变时

from dis import dis

def swap():
    a = [1, 0]
    a[0], a[a[0]] = a[a[0]], a[0]
dis(swap)

swap()

输出是

  4           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               2 (0)
              4 BUILD_LIST               2
              6 STORE_FAST               0 (a)

  5           8 LOAD_FAST                0 (a)
             10 LOAD_FAST                0 (a)
             12 LOAD_CONST               2 (0)
             14 BINARY_SUBSCR
             16 BINARY_SUBSCR
             18 LOAD_FAST                0 (a)
             20 LOAD_CONST               2 (0)
             22 BINARY_SUBSCR
             24 ROT_TWO
             26 LOAD_FAST                0 (a)
             28 LOAD_CONST               2 (0)
             30 STORE_SUBSCR
             32 LOAD_FAST                0 (a)
             34 LOAD_FAST                0 (a)
             36 LOAD_CONST               2 (0)
             38 BINARY_SUBSCR
             40 STORE_SUBSCR
             42 LOAD_CONST               0 (None)
             44 RETURN_VALUE

您可以看到顶部两项相同的输出字节码。 因此它不会交换

也很容易在纸上(例如在求职面试中)考虑它,并且您不需要将代码调试或反汇编为字节码以进行理解。

我也认为它与 C++ 中交换函数的实现没有任何关系。 这些都是不相关的事情。

您只需要知道,首先对右侧进行完全评估,然后将表达式右侧的值按从左到右的顺序分配给左侧的值。 Sophros 以正确的方式回答了这个问题,我只是进一步更详细地扩展了这个想法。

想象第一种情况。 我们有:

a = [2,1,0]

a[0], a[a[0]] = a[a[0]], a[0]

当我们开始执行这段代码时,右边先计算,所以我们会有

a[0], a[a[0]] = a[a[0]], a[0]    # a[a[0]] == 0, a[0] == 2, a == [2, 1, 0]

在右侧,我们有元组(0, 2)并且a仍然是[2, 1, 0]

接下来,我们开始从左侧分配给表达式的左侧,因此我们将元组中的第一项分配给a[0] ,即0 现在我们有

a[0], a[a[0]] = (0, 2)   # a[0] == 0, a == [0, 1, 0]

现在我们执行赋值的最后一部分,即 to a[a[0]] assign 2 但是a[0]现在是0 ,所以在减少之后我们分配给a[0]2 因此最后一次赋值之后的值是

a[0], a[a[0]] = (0, 2)   # a[a[0]] == 2, a == [2, 1, 0]

看起来,什么都没有改变,值也没有交换,但从上面可以看出a[2,1,0] ,然后是[0,1,0] ,最后又是[2,1,0] 所以看起来,没有任何改变,交换不起作用。

现在是第二种情况,我们只更改表达式中变量的顺序:

a = [2,1,0]

a[a[0]], a[0] = a[0], a[a[0]]

当我们开始执行这段代码时,右边先计算,所以我们会有

a[a[0]], a[0] = a[0], a[a[0]]    # a[0] == 2, a[a[0]] == 0, a == [2, 1, 0]

在右侧,我们有元组(2, 0)并且a仍然是[2, 1, 0]

接下来,我们从左侧开始分配给表达式的左侧,因此我们将元组中的第一项分配给a[a[0]] ,即2 a[0]2 ,所以归约后,我们分配给a[2]2 现在我们有

a[a[0]], a[0] = (2, 0)   # a[a[0]] == 2, a == [2, 1, 2]

现在我们执行赋值的最后一部分,即 to a[0] assign 0 因此最后一次赋值之后的值是

a[a[0]], a[0] = (2, 0)   # a[0] == 0, a == [0, 1, 2]

现在这按预期工作。

因此,当您的交换表达式中有因变量时,还需要考虑顺序。 作为因变量,我的意思是在第一种情况下,我们在左侧有a[0], a[a[0]] ,这意味着a[0]改变它的值,而a[a[0]]使用这个改变后的值,这会导致不必要的行为。

最后,无论编程语言如何,当您想要交换它们的值时,最好不要使用因变量(另一个数组索引的数组索引等)。

Python 和 C++ 是不同的语言,具有不同的规则。 这就是为什么表面上相似的结构在这些语言中表现不同的主要原因。

您不能在 Python 中编写通用swap来处理诸如a[0], a[a[0]] 这不是问题。 你不应该试图用任何语言来交换这些东西,以避免混淆并提高你未来作为程序员就业的机会。

如果您绝对需要交换由同一数组的元素索引的数组元素,则可以在 Python 中这样做:

p, q, a[p], a[q] = index0, index1, a[q], a[p]

其中index0index1可以是涉及a[i]a[a[i]]a[a[a[i]]]或任何类似内容的任何表达式。 例如

p, q,  a[p], a[q] = a[0], a[a[0]],  a[q], a[p]

作品。

所以,这实际上是 python 的一个特性,称为多重赋值,如果你来自其他语言,有时很难掌握。 这就是它的工作原理。

例如。 a, b = b, a

这段代码实际上会交换元素。 我将给出一个简单直观的解释,然后是一个更具技术性的解释。

  1. 首先评估 RHS,然后将值分别分配给 LHS 上的变量(标签)。
  2. 所以你实际上可以在 python 中将元组 (a,b) 定义为 a,b,括号只是为了更好的可读性。 所以你的 RHS 是一个解包的元组,每个元素分别分配给 LHS 上的每个标签。 所以下面所有的代码片段都是等价的。
a, b = b, a
a, b = (b, a)
a, b = [b, a]

注意:python 中变量的概念与其他语言非常不同,在其他语言中,它们是某种类型的容器来存储该类型的值。 在 python 中,标签是比变量更正确的术语,因为您只是用这个名称标记一个对象,并且该对象可以是任何数据类型。 所以为了理解这段代码,当你做a,b = b,a ,你实际上并不是在交换值,而是在交换标签
因此,python 首先在 RHS 上查找标记ba指向的值,将这些值放在那里,然后它只是为这些值提供新标签。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM