Czy istnieje ustandaryzowana metoda zamiany dwóch zmiennych w Pythonie?

345

W Pythonie widziałem dwie zmienne wartości zamienione za pomocą tej składni:

left, right = right, left

Czy jest to uważane za standardowy sposób zamiany dwóch wartości zmiennych, czy też istnieją inne sposoby, za pomocą których zwykle dwie zmienne są zwykle zamieniane?

WilliamKF
źródło
1
@eyquem: sprowadza się to po prostu do tego , czy kolejność oceny jest definiowana przez język przypisania krotki / listy. Python tak, większość starszych języków nie.
smci
Hrmm C ++ ma zamianę (a [i], a [k]), dlaczego nie możemy mieć czegoś takiego dla Pythona.
Nils,

Odpowiedzi:

389

Python ocenia wyrażenia od lewej do prawej. Zauważ, że podczas oceny zadania prawa strona jest oceniana przed lewą stroną.

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

Oznacza to, że wyrażenie a,b = b,a:

  • b,aoceniana jest prawa strona , to znaczy krotka dwóch elementów jest tworzona w pamięci. Dwa elementem są obiekty oznaczone identyfikatorami bi a, które zostały wcześniej istniejących dyspozycja jest encoutered trakcie realizacji programu
  • tuż po utworzeniu tej krotki nie dokonano jeszcze przypisania tego obiektu krotki, ale to nie ma znaczenia, Python wewnętrznie wie, gdzie on jest
  • następnie oceniana jest lewa strona, to znaczy krotka jest przypisana do lewej strony
  • ponieważ lewa strona składa się z dwóch identyfikatorów, krotka jest rozpakowywana w celu aprzypisania pierwszego identyfikatora pierwszemu elementowi krotki (czyli obiektowi, który był poprzednio b przed zamianą, ponieważ miał nazwę b)
    oraz drugi identyfikator bjest przyporządkowana do drugiego elementu krotki (która jest obiektem, który był poprzednio przed wymiany, ponieważ jej identyfikatory było )a

Ten mechanizm skutecznie zamienił obiekty przypisane do identyfikatorów aib

Tak więc, aby odpowiedzieć na twoje pytanie: TAK, jest to standardowy sposób zamiany dwóch identyfikatorów na dwa obiekty.
Nawiasem mówiąc, obiekty nie są zmiennymi, są obiektami.

eyquem
źródło
1
O ile rozumiem, zamiana dwóch zmiennych w ten sposób NIE wymaga dodatkowej pamięci, tylko pamięć samych 2 zmiennych, czy mam rację?
Catbuilts
1
@Catbuilts Skonstruowanie krotki zajmie dodatkową pamięć (prawdopodobnie zajmie więcej niż wersja zamiany z 3 zmiennymi), ale ponieważ wymieniane są tylko adresy pamięci, absolutnie nie będzie to dużo dodatkowej pamięci sens (może 24 dodatkowe bajty).
Brilliand
@Brilliand: Thks. Czy masz jakieś dokumenty dotyczące tego tematu? To dość interesujące i chciałbym przeczytać jeszcze raz. Dzięki.
Catbuilts,
1
@Catbuilts Nie jestem pewien, ale może pomóc przeczytać o tym, jak działają wskaźniki C ++. Podczas gdy Python próbuje automatycznie robić rzeczy w najlepszy dla Ciebie sposób, C ++ faktycznie daje ci wszystkie możliwości robienia rzeczy na wszystkie możliwe dobre i złe sposoby, więc jest to dobry punkt wyjścia do poznania wad podejścia, które przyjmuje Python . Pamiętaj również, że posiadanie „64-bitowego” systemu operacyjnego oznacza, że ​​przechowywanie adresu pamięci zajmuje 64 bity pamięci - to jest część, z której otrzymałem mój numer „24 bajtów”.
Brilliand
Świetne wyjaśnienie. Dodając, że właśnie dlatego możesz również użyć tej metody do zmiany dowolnej liczby „zmiennych”, np a, b, c = c, a, b.
alexlomba87
109

Jest to standardowy sposób zamiany dwóch zmiennych, tak.

Martijn Pieters
źródło
38

Znam trzy sposoby zamiany zmiennych, ale a, b = b, ajest najprostsza. Jest

XOR (dla liczb całkowitych)

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

Lub zwięźle

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

Zmienna tymczasowa

w = x
x = y
y = w
del w

Tuple swap

x, y = y, x
Nikogo tu nie ma
źródło
1
Jest najprostszy i jedyny, który nie jest zaciemniony.
Jorge Leitao
17
XOR nie zamienia „zmiennych”. Zamienia zmienne całkowite. (Lub kilka innych typów poprawnie implementujących operatora XOR) Ponadto, ponieważ zgodnie z odpowiedzią Rogalskiego, Tuple Swap jest zoptymalizowany w interpreter, tak naprawdę nie ma nic przeciwko temu. Krótko, wyraźnie i szybko.
Rawler,
Problemu XOR można uniknąć przez użycie operatora +, ale nadal uważam, że najlepiej jest a, b = b, a codex = x + yy = xy x = xycode
ashish
22

Nie powiedziałbym, że jest to standardowy sposób zamiany, ponieważ spowoduje nieoczekiwane błędy.

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

nums[i]zostanie najpierw zmodyfikowany, a następnie wpłynie na drugą zmienną nums[nums[i] - 1].

Yi Huang
źródło
2
Problem występuje w prawie każdym języku programowania, dlatego nie jest bezpieczne używanie swap (a, b), jeśli a zależy od b lub odwrotnie. Na przykład, wymiany (a, b) może być rozszerzony do: var c=a, a=b, b=c. I wtedy ostatnie przypisanie użyje nowej wartości ado oceny adresu b.
Kai Petzke,
1
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]. Rozwiązałoby problem.
Bill Cheng,
2
@JacksonKelley Ocena prawej strony jest bezpieczna. W nums[i], nums[nums[i] - 1] = nums[nums[i] - 1], nums[i]: Problem polega na tym, że Python wykonuje przypisanie po lewej stronie, nums[i]ulega zmianie, co powoduje nums[nums[i] - 1]nieoczekiwane zmiany. Na początku możesz sobie wyobrazić, że chcesz nums[1],nums[2] = nums[2],nums[1], ale po nums[1] = nums[2]biegu już nie masz nums[2] = nums[1], zamiast tego masz nums[888] = nums[1].
guo
5

Nie działa w przypadku tablic wielowymiarowych, ponieważ używane są tutaj odniesienia.

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)

Zobacz także Zamień plastry tablic Numpy

gizzmole
źródło
To jest rzeczywiście specjalna funkcja (lub błąd) biblioteki numpy.
Kai Petzke,
-1

Aby obejść problemy wyjaśnione przez eyquem , możesz użyć copymodułu do zwrócenia krotki zawierającej (odwrócone) kopie wartości za pomocą funkcji:

from copy import copy

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

Ta sama funkcja jak lambda:

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

Następnie przypisz je do żądanych nazw, takich jak to:

x, y = swapper(y, x)

UWAGA: jeśli chcesz, możesz zaimportować / użyć deepcopyzamiast copy.

LogicalBranch
źródło
jaki problem próbujesz rozwiązać przez kopiowanie?
Hanan Shteingart
Te omówione w poście eyquem .
LogicalBranch
1
ale nie oznacza to żadnego problemu, w rzeczywistości mówi „TAK, to standardowy sposób zamiany dwóch identyfikatorów”
Hanan Shteingart
-2

Możesz łączyć zamiany krotki i 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))

lub

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))

Za pomocą 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))

Wynik:

Before swapping: x =  10 , y =  20
After swapping: x =  20 , y =  10
u-betcha
źródło