是否有一种标准化的方法可以在 Python 中交换两个变量?

Posted

技术标签:

【中文标题】是否有一种标准化的方法可以在 Python 中交换两个变量?【英文标题】:Is there a standardized method to swap two variables in Python? 【发布时间】:2013-01-27 23:26:11 【问题描述】:

在 Python 中,我已经看到使用这种语法交换了两个变量值:

left, right = right, left

这是否被认为是交换两个变量值的标准方法,还是有一些其他方法可以按照惯例最常交换两个变量?

【问题讨论】:

@eyquem:归结为 order-of-evaluation 是否由元组/列表分配的语言定义。 Python 有,大多数旧语言没有。 Hrmm C++ 有 swap(a[i], a[k]) 为什么我们不能为 Python 提供这样的东西。 @Nils 因为在 Python 中,赋值是 aliasing 操作,而在 C++ 中,赋值到引用替换手术。因此,在 Python 中,您不能像在 C++ 中那样替换传递给函数的参数值(您只能改变它们)。有关这些术语的解释,请参阅 Grogono 和 Sakkinen 的 Copying and Comparing: Problems and Solutions 【参考方案1】:

Python 从左到右计算表达式。请注意,虽然 评估分配,右手边在评估之前 左侧。

Python docs: Evaluation order

这意味着 a,b = b,a 的表达式如下:

右边b,a被求值,也就是说,在内存中创建了两个元素的元组。这两个元素是由标识符ba 指定的对象,它们在程序执行期间遇到指令之前就已经存在。 刚创建完这个元组之后,还没有对这个元组对象进行赋值,不过没关系,Python内部知道它在哪里。 然后,对左侧进行求值,也就是说,将元组分配给左侧。 由于左侧由两个标识符组成,元组被解包,以便将第一个标识符 a 分配给元组的第一个元素(以前是 b 在交换之前,因为它的名称为 b) 并且第二个标识符 b 分配给元组的第二个元素(这是在交换之前以前是 a 的对象,因为它的标识符是 a

这种机制有效地交换了分配给标识符ab的对象

所以,回答您的问题:是的,这是在两个对象上交换两个标识符的标准方法。 顺便说一句,对象不是变量,它们是对象。

【讨论】:

据我了解,以这种方式交换两个变量不会使用额外的内存,只是内存用于 2 个变量本身,对吗? @Catbuilts 构造元组会占用一些额外的内存(可能比 3 变量版本的交换会占用更多),但由于唯一交换的东西是内存地址,它不会绝对意义上的额外内存(可能是 24 个额外字节)。 @Brilliand:谢谢。你有这个主题的任何文件吗?这很有趣,我想进一步阅读。谢谢。 @Catbuilts 我不确定,但了解 C++ 指针的工作原理可能会有所帮助。 Python 试图自动以最佳方式为您做事,而 C++ 实际上为您提供了以所有可能正确和错误的方式做事的所有选项,因此这是了解 Python 采用的方法的缺点的一个很好的起点.还要记住,拥有“64 位”操作系统意味着存储内存地址会占用 64 位内存——这是我获得“24 字节”数字的部分原因。 如果您将@martijn-pieters 的答案放在文章顶部,这可能是公认的答案:“这是交换两个变量的标准方法,是的。”【参考方案2】:

这是交换两个变量的标准方法,是的。

【讨论】:

【参考方案3】:

我知道三种交换变量的方法,但a, b = b, a 是最简单的。有

XOR(整数)

x = x ^ y
y = y ^ x
x = x ^ y

或者简而言之,

x ^= y
y ^= x
x ^= y

临时变量

w = x
x = y
y = w
del w

元组交换

x, y = y, x

【讨论】:

是最简单的,也是唯一没有混淆的。 XOR 不会交换“变量”。它交换整数变量。 (或其他少数正确实现 XOR 运算符的类型)此外,由于根据 Rogalski 的回答,元组交换在解释器中进行了优化,因此实际上没有什么反对的。简短、清晰、快速。 XOR 问题可以通过 + - 运算符的使用来避免,但我仍然觉得最好是 a, b = b, a code x = x+yy = xy x = xy code跨度> 如果是整数值,你也可以用简单的算术来做【参考方案4】:

我不会说这是一种标准的交换方式,因为它会导致一些意想不到的错误。

nums[i], nums[nums[i] - 1] = nums[nums[i] - 1], nums[i]

nums[i]会先被修改,再影响第二个变量nums[nums[i] - 1]

【讨论】:

几乎任何编程语言都有这个问题,如果 a 依赖于 b,则使用 swap(a,b) 是不安全的,反之亦然。例如,swap(a,b) 可以扩展为:var c=aa=bb=c。然后,最后的赋值将使用a的新值来评估b的地址。 nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]。可以解决问题。 @JacksonKelley 右侧评估是安全的。在nums[i], nums[nums[i] - 1] = nums[nums[i] - 1], nums[i]:问题是当python进行左侧赋值时,nums[i]被改变了,这导致nums[nums[i] - 1]被意外改变。你可以想象一开始你想要nums[1],nums[2] = nums[2],nums[1],但是在nums[1] = nums[2]运行之后,你不再有nums[2] = nums[1],而是你得到了nums[888] = nums[1] 哇,解决时遇到这个问题leetcode.com/problems/first-missing-positive/description【参考方案5】:

不适用于多维数组,因为这里使用了引用。

import numpy as np

# swaps
data = np.random.random(2)
print(data)
data[0], data[1] = data[1], data[0]
print(data)

# does not swap
data = np.random.random((2, 2))
print(data)
data[0], data[1] = data[1], data[0]
print(data)

另见Swap slices of Numpy arrays

【讨论】:

这确实是numpy库的一个特殊功能(或bug)。【参考方案6】:

要解决eyquem 解释的问题,您可以使用copy 模块通过函数返回一个包含(反转)值副本的元组:

from copy import copy

def swapper(x, y):
  return (copy(y), copy(x))

lambda的功能相同:

swapper = lambda x, y: (copy(y), copy(x))

然后,将它们分配给所需的名称,如下所示:

x, y = swapper(y, x)

注意:如果您愿意,可以导入/使用 deepcopy 而不是 copy

【讨论】:

你试图通过复制解决什么问题? eyquem's post 中讨论的那些。 但它并没有说明任何问题,实际上他说“是的,这是交换两个标识符的标准方式”【参考方案7】:

该语法是交换变量的标准方法。但是,在处理被修改然后在交换的后续存储元素中使用的元素时,我们需要注意顺序。

使用带有直接索引的数组很好。例如:

def swap_indexes(A, i1, i2):
      A[i1], A[i2] = A[i2], A[i1]
      print('A[i1]=', A[i1], 'A[i2]=', A[i2])
      return A

  A = [0, 1, 2, 3, 4]
  print('For A=', A)
  print('swap indexes 1, 3:', swap_indexes(A, 1, 3))

给我们: ('对于 A=', [0, 1, 2, 3, 4]) ('A[i1]=', 3, 'A[i2]=', 1) ('交换索引 1, 3:', [0, 3, 2, 1, 4])

但是,如果我们更改左侧第一个元素并在左侧第二个元素中使用它作为索引,这会导致交换错误。

def good_swap(P, i2):
    j = P[i2]
    #Below is correct, because P[i2] is modified after it is used in P[P[i2]]
    print('Before: P[i2]=', P[i2], 'P[P[i2]]=', P[j])
    P[P[i2]], P[i2] = P[i2], P[P[i2]]
    print('Good swap: After P[i2]=', P[i2], 'P[P[i2]]=', P[j])
    return P

def bad_swap(P, i2):
    j = P[i2]
    #Below is wrong, because P[i2] is modified and then used in P[P[i2]]
    print('Before: P[i2]=', P[i2], 'P[P[i2]]=', P[j])
    P[i2], P[P[i2]] = P[P[i2]], P[i2]
    print('Bad swap: After P[i2]=', P[i2], 'P[P[i2]]=', P[j])
    return P

P = [1, 2, 3, 4, 5]
print('For P=', P)
print('good swap with index 2:', good_swap(P, 2))
print('------')
P = [1, 2, 3, 4, 5]
print('bad swap with index 2:', bad_swap(P, 2))

('对于 P=', [1, 2, 3, 4, 5]) ('之前:P[i2]=', 3, 'P[P[i2]]=', 4) ('好的交换:P[i2]='之后,4,'P[P[i2]]=',3) ('与索引 2 的良好交换:', [1, 2, 4, 3, 5])

('之前:P[i2]=', 3, 'P[P[i2]]=', 4) ('Bad swap: After P[i2]=', 4, 'P[P[i2]]=', 4) ('bad swap with index 2:', [1, 2, 4, 4, 3])

坏交换是不正确的,因为 P[i2] 是 3,我们期望 P[P[i2]] 是 P[3]。但是P[i2]先变成了4,所以后面的P[P[i2]]变成了P[4],覆盖的是第4个元素而不是第3个元素。

上述场景用于排列。更简单的好交换和坏交换是:

#good swap:
P[j], j = j, P[j]
#bad swap:
j, P[j] = P[j], j

【讨论】:

【参考方案8】:

您可以组合 tupleXOR 交换:x, y = x ^ x ^ y, x ^ y ^ y

x, y = 10, 20

print('Before swapping: x = %s, y = %s '%(x,y))

x, y = x ^ x ^ y, x ^ y ^ y

print('After swapping: x = %s, y = %s '%(x,y))

x, y = 10, 20

print('Before swapping: x = %s, y = %s '%(x,y))

print('After swapping: x = %s, y = %s '%(x ^ x ^ y, x ^ y ^ y))

使用 lambda

x, y = 10, 20

print('Before swapping: x = %s, y = %s' % (x, y))

swapper = lambda x, y : ((x ^ x ^ y), (x ^ y ^ y))

print('After swapping: x = %s, y = %s ' % swapper(x, y))

输出:

Before swapping: x =  10 , y =  20
After swapping: x =  20 , y =  10

【讨论】:

以上是关于是否有一种标准化的方法可以在 Python 中交换两个变量?的主要内容,如果未能解决你的问题,请参考以下文章

是否有一种标准方法可以向您的 Web API 验证应用程序?

是否有一种标准方法来捕获适用于 linux 和 windows 的 c++ 应用程序中的键盘输入?

将版本嵌入 Python 包的标准方法?

是否有一种简单的方法可以在对象级别上记住(和刷新)Python上的属性?

是否有一种可移植的(C++ 标准)方法来计算前一个对齐的指针?

在 Python 中将 N 秒添加到 datetime.time 的标准方法是啥?