Есть ли стандартизированный метод для замены двух переменных в Python?

345

В Python я видел два значения переменных, которые меняются местами с использованием этого синтаксиса:

left, right = right, left

Считается ли это стандартным способом обмена двумя значениями переменных или есть какие-то другие средства, по которым две переменные обычно обмениваются чаще всего?

WilliamKF
источник
1
@eyquem: все сводится к тому, определяется ли порядок оценки языком для назначения кортежа / списка. Python делает, большинство старых языков нет.
SMCI
Hrmm C ++ имеет своп (a [i], a [k]), почему у нас не может быть что-то подобное для Python.
Нильс

Ответы:

389

Python оценивает выражения слева направо. Обратите внимание, что при оценке присваивания правая сторона оценивается перед левой стороной.

http://docs.python.org/3/reference/expressions.html#evaluation-order

Это означает следующее для выражения a,b = b,a:

  • b,aоценивается правая часть , то есть в памяти создается кортеж из двух элементов. Два элемента - это объекты, обозначенные идентификаторами bиa существовавшие до выполнения инструкции во время выполнения программы.
  • Сразу после создания этого кортежа этот объект кортежа еще не был назначен, но это не имеет значения, Python внутренне знает, где он находится.
  • затем оценивается левая сторона, то есть кортеж назначается левой стороне
  • поскольку левая часть состоит из двух идентификаторов, кортеж распаковывается для того, чтобы первый идентификатор aбыл назначен первому элементу кортежа (то есть объекту, который ранее был b перед свопом, потому что он имел имя b)
    и Второй идентификатор bприсваивается второму элементу кортежа (который является объектом, который ранее был до обмена, потому что его идентификаторы были a)

Этот механизм эффективно поменял местами объекты, назначенные идентификаторам, aиb

Итак, чтобы ответить на ваш вопрос: ДА, это стандартный способ поменять два идентификатора на два объекта.
Кстати, объекты не являются переменными, они являются объектами.

Eyquem
источник
1
Насколько я понимаю, замена двух переменных таким способом НЕ использует дополнительную память, только память для самих двух переменных, я прав?
Catbuilts
1
@Catbuilts Построение кортежа потребует некоторой дополнительной памяти (вероятно, больше, чем заняла бы своп-версия с 3 переменными), но поскольку единственные вещи, которые меняются местами, - это адреса памяти, в абсолютной памяти не будет много дополнительной памяти. смысл (возможно 24 дополнительных байта).
Brilliand
@Brilliand: Спасибо. Есть ли у вас какие-либо документы на эту тему. Это довольно интересно, и я хотел бы прочитать дальше. Спасибо.
Catbuilts
1
@Catbuilts Я не уверен, но это может помочь понять, как работают указатели C ++. Принимая во внимание, что Python пытается автоматически сделать все наилучшим образом для вас, C ++ фактически предоставляет вам все возможности для выполнения действий всеми возможными правильными и неправильными способами, так что это хорошая отправная точка для изучения недостатков подхода, который использует Python. , Также помните, что наличие «64-битной» операционной системы означает, что хранение адреса памяти занимает 64 бита памяти - это часть того, откуда я получил свой «24-байтовый» номер.
Brilliand
Отличное объяснение. Просто добавив, что именно поэтому вы можете использовать этот метод для перестановки любого количества «переменных», например a, b, c = c, a, b.
alexlomba87
109

Это стандартный способ поменять две переменные, да.

Мартейн Питерс
источник
38

Я знаю три способа обмена переменными, но 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

Tuple swap

x, y = y, x
NoOneIsHere
источник
1
Это самый простой и единственный, который не запутывается.
Хорхе Лейтао
17
XOR не меняет местами «переменные». Меняет местами целочисленные переменные. (Или несколько других типов, правильно реализующих оператор XOR) Кроме того, поскольку, согласно ответу Рогальского, в интерпретаторе оптимизирован обмен кортежами, в этом нет ничего против. Коротко, ясно и быстро.
Роулер
Проблемы с XOR можно избежать, если использовать оператор + -, но все же я чувствую, что лучше всего: a, b = b, a codex = x + yy = xy x = xycode
ashish
22

Я бы не сказал, что это стандартный способ обмена, потому что это приведет к неожиданным ошибкам.

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

nums[i]будет изменен первым, а затем повлияет на вторую переменную nums[nums[i] - 1].

И Хуан
источник
2
У вас проблема почти на любом языке программирования, то есть использование swap (a, b) небезопасно, если a зависит от b или наоборот. Например, замена (а, б) может быть расширена до: var c=a, a=b, b=c. И затем последнее назначение будет использовать новое значение aдля оценки адреса b.
Кай Петцке
1
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1], Решил бы проблему.
Билл Ченг
2
@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].
Го
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)

См. Также Сменные фрагменты массивов Numpy.

gizzmole
источник
Это действительно особенность (или ошибка) библиотеки numpy.
Кай Петцке
-1

Чтобы обойти проблемы, объясняемые 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.

LogicalBranch
источник
Какую проблему вы пытаетесь решить с помощью копирования?
Ханан Штейнгарт
Те, которые обсуждались в сообщении эйкема .
LogicalBranch
1
но это не говорит о каких-либо проблемах, фактически он говорит: «ДА, это стандартный способ поменять два идентификатора»
Ханан Штейнгарт,
-2

Вы можете комбинировать перестановки кортежей и XOR : 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))

Используя лямбду :

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
U-Betcha
источник