Existe um método padronizado para trocar duas variáveis ​​no Python?

345

No Python, vi dois valores de variáveis ​​trocados usando esta sintaxe:

left, right = right, left

Essa é considerada a maneira padrão de trocar dois valores de variável ou há algum outro meio pelo qual duas variáveis ​​são por convenção geralmente trocadas?

WilliamKF
fonte
11
@eyquem: tudo se resume a se a ordem de avaliação é definida pelo idioma para uma atribuição de tupla / lista. Python faz, a maioria das linguagens antigas não.
SMCI
Hrmm C ++ tem swap (a [i], a [k]) por que não podemos ter algo assim para Python.
Nils

Respostas:

389

Python avalia expressões da esquerda para a direita. Observe que, ao avaliar uma tarefa, o lado direito é avaliado antes do lado esquerdo.

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

Isso significa o seguinte para a expressão a,b = b,a:

  • o lado direito b,aé avaliado, ou seja, uma tupla de dois elementos é criada na memória. Os dois elementos são os objetos designados pelos identificadores be aque existiam antes da instrução ser descoberta durante a execução do programa.
  • logo após a criação dessa tupla, nenhuma atribuição desse objeto ainda foi feita, mas isso não importa, o Python sabe internamente onde está
  • então, o lado esquerdo é avaliado, ou seja, a tupla é atribuída ao lado esquerdo
  • como o lado esquerdo é composto por dois identificadores, a tupla é descompactada para que o primeiro identificador aseja atribuído ao primeiro elemento da tupla (que é o objeto que era formalmente b antes da troca porque tinha nome b)
    e o o segundo identificador bé atribuído ao segundo elemento da tupla (que é o objeto que era anteriormente um antes da troca porque seus identificadores eram a)

Esse mecanismo efetivamente trocou os objetos atribuídos aos identificadores aeb

Portanto, para responder à sua pergunta: SIM, é a maneira padrão de trocar dois identificadores em dois objetos.
A propósito, os objetos não são variáveis, são objetos.

eyquem
fonte
11
Tanto quanto eu entendo, trocar duas variáveis ​​dessa maneira NÃO usa memória extra, apenas memória para duas variáveis, estou certo?
Catbuilts 11/05/19
11
@Catbuilts A construção da tupla ocupará um pouco de memória extra (provavelmente mais do que a versão de 3 variáveis ​​da troca consumiria), mas como as únicas coisas que estão sendo trocadas são os endereços de memória, não haverá muita memória extra no absoluto sentido (talvez 24 bytes extras).
Brilliand
@ Brilliand: Thks. Você tem algum documento para este assunto? É bem interessante e eu gostaria de ler mais. Obrigado.
Catbuilts 16/09/19
11
@ Catbuilts Não tenho certeza, mas pode ajudar a ler como os ponteiros C ++ funcionam. Enquanto o Python tenta fazer as coisas automaticamente da melhor maneira para você, o C ++ realmente oferece todas as opções para fazer as coisas da maneira certa e errada, então esse é um bom ponto de partida para aprender quais são as desvantagens da abordagem adotada pelo Python. . Lembre-se também de que ter um sistema operacional "64 bits" significa que o armazenamento de um endereço de memória ocupa 64 bits - é parte de onde obtive meu número de "24 bytes".
Brilliand
Ótima explicação. Apenas adicionando que é por isso que você também pode usar esse método para reorganizar qualquer número de "variáveis", por exemplo a, b, c = c, a, b.
alexlomba87
109

Essa é a maneira padrão de trocar duas variáveis, sim.

Martijn Pieters
fonte
38

Conheço três maneiras de trocar variáveis, mas a, b = b, aé a mais simples. Há sim

XOR (para números inteiros)

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

Ou concisa,

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

Variável temporária

w = x
x = y
y = w
del w

Troca de tupla

x, y = y, x
Ninguém está aqui
fonte
11
É o mais simples e o único que não é ofuscado.
Jorge Leitao
17
O XOR não troca "variáveis". Ele troca variáveis ​​inteiras. (Ou os poucos outros tipos que implementam corretamente o operador XOR) Além disso, como de acordo com a resposta de Rogalski, o Tuple Swap é otimizado no intérprete, não há realmente nada contra ele. Curto, claro e rápido.
Rawler
Questão XOR pode ser evitado + - uso do operador, mas ainda me sinto melhor é a, b = b, um codex = x + yy = xy x = xycode
ashish
22

Eu não diria que é uma maneira padrão de trocar porque causará alguns erros inesperados.

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

nums[i]será modificado primeiro e depois afetará a segunda variável nums[nums[i] - 1].

Yi Huang
fonte
2
Você tem o problema em quase qualquer linguagem de programação, que não é seguro usar swap (a, b), se a depender de b ou vice-versa. Por exemplo, swap (a, b) pode ser expandida para: var c=a, a=b, b=c. E então, a última tarefa usará o novo valor de apara avaliar o endereço de b.
Kai Petzke
11
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]. Resolveria o problema.
Bill Cheng
11
Isso resolve o problema, mas por quê?
Jackson Kelley
2
@JacksonKelley A avaliação do lado direito é segura. In nums[i], nums[nums[i] - 1] = nums[nums[i] - 1], nums[i]: O problema é quando o python faz a atribuição do lado esquerdo, nums[i]é alterado, o que causa nums[nums[i] - 1]alterações inesperadas. Você pode imaginar a princípio que deseja nums[1],nums[2] = nums[2],nums[1], mas depois da nums[1] = nums[2]corrida, você não precisa mais nums[2] = nums[1], em vez disso, conseguiu nums[888] = nums[1].
guo 5/03
5

Não funciona para matrizes multidimensionais, porque as referências são usadas aqui.

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)

Consulte também Trocar fatias de matrizes Numpy

gizzmole
fonte
Este é realmente um recurso especial (ou bug) da biblioteca numpy.
Kai Petzke
-1

Para contornar os problemas explicados pelo eyquem , você pode usar o copymódulo para retornar uma tupla contendo cópias (revertidas) dos valores, através de uma função:

from copy import copy

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

Mesma função que lambda:

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

Em seguida, atribua-os aos nomes desejados, assim:

x, y = swapper(y, x)

NOTA: se você quiser, poderá importar / usar em deepcopyvez de copy.

LogicalBranch
fonte
qual é o problema que você está tentando resolver por cópia?
Hanan Shteingart 24/06/19
Os discutidos no post de eyquem .
LogicalBranch
11
mas não apresenta nenhum problema, na verdade ele diz "SIM, é a maneira padrão de trocar dois identificadores"
Hanan Shteingart
-2

Você pode combinar trocas de tupla e 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))

ou

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

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

Resultado:

Before swapping: x =  10 , y =  20
After swapping: x =  20 , y =  10
u-betcha
fonte