Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

224
Visualizações
Comprenda el intercambio de Python: ¿por qué a, b = b, a no siempre es equivalente a b, a = a, b?

Como todos sabemos, la forma pitónica de intercambiar los valores de dos elementos a y b es

 a, b = b, a

y debe ser equivalente a

 b, a = a, b

Sin embargo, hoy, cuando estaba trabajando en un código, descubrí accidentalmente que los siguientes dos intercambios dan resultados diferentes:

 nums = [1, 2, 4, 3] i = 2 nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i] print(nums) # [1, 2, 4, 3] nums = [1, 2, 4, 3] i = 2 nums[nums[i]-1], nums[i] = nums[i], nums[nums[i]-1] print(nums) # [1, 2, 3, 4]

Esto es alucinante para mí. ¿Alguien puede explicarme qué pasó aquí? Pensé que en un intercambio de Python las dos asignaciones ocurren de forma simultánea e independiente.

over 4 years ago · Hanz Gallego
8 Respostas
Responde à pergunta

0

Para comprender el orden de evaluación, hice una clase 'Variable' que imprime cuando se establece y se obtiene su 'valor'.

 class Variable: def __init__(self, name, value): self._name = name self._value = value @property def value(self): print(self._name, 'get', self._value) return self._value @value.setter def value(self): print(self._name, 'set', self._value) self._value = value a = Variable('a', 1) b = Variable('b', 2) a.value, b.value = b.value, a.value

Cuando se ejecuta da como resultado:

 b get 2 a get 1 a set 2 b set 1

Esto muestra que primero se evalúa el lado derecho (de izquierda a derecha) y luego se evalúa el lado izquierdo (nuevamente, de izquierda a derecha).

Con respecto al ejemplo de OP: el lado derecho evaluará los mismos valores en ambos casos. Se establece el primer término del lado izquierdo y esto afecta la evaluación del segundo término. Nunca se evaluó de manera simultánea e independiente, es solo que la mayoría de las veces que se usa esto, los términos no dependen unos de otros. Establecer un valor en una lista y luego tomar un valor de esa lista para usarlo como índice en la misma lista generalmente no es una cosa y si eso es difícil de entender, lo entiende. Como cambiar la longitud de una lista en un bucle for es malo, esto tiene el mismo olor. (Sin embargo, una pregunta estimulante, como habrás adivinado porque corrí hacia un bloc de notas)

over 4 years ago · Hanz Gallego Relatório

0

Una forma de analizar fragmentos de código en CPython es desmontar su código de bytes para su máquina de pila simulada.

 >>> import dis >>> dis.dis("nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i]") 1 0 LOAD_NAME 0 (nums) 2 LOAD_NAME 0 (nums) 4 LOAD_NAME 1 (i) 6 BINARY_SUBSCR 8 LOAD_CONST 0 (1) 10 BINARY_SUBTRACT 12 BINARY_SUBSCR 14 LOAD_NAME 0 (nums) 16 LOAD_NAME 1 (i) 18 BINARY_SUBSCR 20 ROT_TWO 22 LOAD_NAME 0 (nums) 24 LOAD_NAME 1 (i) 26 STORE_SUBSCR 28 LOAD_NAME 0 (nums) 30 LOAD_NAME 0 (nums) 32 LOAD_NAME 1 (i) 34 BINARY_SUBSCR 36 LOAD_CONST 0 (1) 38 BINARY_SUBTRACT 40 STORE_SUBSCR 42 LOAD_CONST 1 (None) 44 RETURN_VALUE

Agregué las líneas en blanco para facilitar la lectura. Las dos expresiones de búsqueda se calculan en los bytes 0-13 y 14-19. BINARY_SUBSCR reemplaza los dos valores superiores en la pila, un objeto y un subíndice, con el valor obtenido del objeto. Los dos valores obtenidos se intercambian para que el primero calculado sea el primer límite. Las dos operaciones de almacenamiento se realizan en los bytes 22-27 y 28-41. STORE_SUBSCR usa y elimina los tres primeros valores de la pila, un valor para almacenar, un objeto y un subíndice. (La parte de retorno Ninguno aparentemente siempre se agrega al final). La parte importante de la pregunta es que los cálculos para las tiendas se realizan secuencialmente en lotes separados e independientes.

La descripción más cercana en Python del cálculo de CPython requiere la introducción de una variable de pila

 stack = [] stack.append(nums[nums[i]-1]) stack.append(nums[i]) stack.reverse() nums[i] = stack.pop() nums[nums[i]-1] = stack.pop()

Aquí está el desmontaje de la declaración invertida

 >>> dis.dis("nums[nums[i]-1], nums[i] = nums[i], nums[nums[i]-1]") 1 0 LOAD_NAME 0 (nums) 2 LOAD_NAME 1 (i) 4 BINARY_SUBSCR 6 LOAD_NAME 0 (nums) 8 LOAD_NAME 0 (nums) 10 LOAD_NAME 1 (i) 12 BINARY_SUBSCR 14 LOAD_CONST 0 (1) 16 BINARY_SUBTRACT 18 BINARY_SUBSCR 20 ROT_TWO 22 LOAD_NAME 0 (nums) 24 LOAD_NAME 0 (nums) 26 LOAD_NAME 1 (i) 28 BINARY_SUBSCR 30 LOAD_CONST 0 (1) 32 BINARY_SUBTRACT 34 STORE_SUBSCR 36 LOAD_NAME 0 (nums) 38 LOAD_NAME 1 (i) 40 STORE_SUBSCR 42 LOAD_CONST 1 (None) 44 RETURN_VALUE
over 4 years ago · Hanz Gallego Relatório

0

Me parece que esto solo sucedería cuando el contenido de la lista esté en el rango de índices de lista para la lista. Si por ejemplo:

 nums = [10, 20, 40, 30]

El código fallará con:

 >>> nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range

Así que definitivamente, un gotcha. Nunca use nunca el contenido de una lista como índice de esa lista.

over 4 years ago · Hanz Gallego Relatório

0

Thierry dio una buena respuesta, déjame ser más claro. Tenga en cuenta que si nums = [1, 2, 4, 3] ,

en este código:

 nums[nums[i]-1], nums[i]
  • yo tengo 2,
  • nums[nums[i]-1] es nums[4-1], entonces nums[3], (el valor es 3)
  • nums[i] es nums[2], (el valor es 4)
  • el resultado es: (3, 4)

en este código:

 nums[i], nums[nums[i]-1]
  • nums[i] es nums[2] se convierte en 3, (=>[1, 2, 3 , 3])
  • pero nums[nums[i]-1] no es nums[ 4 -1] sino nums[ 3 -1], por lo que nums[2] también se convierte en (de vuelta a) 4 (=>[1, 2, 4 , 3 ])

Quizás la buena pregunta sobre un intercambio fue usar:

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

Intentalo:

 >>> print(nums) >>> [1, 2, 4, 3] >>> nums[i], nums[i-1] = nums[i-1], nums[i] >>> print(nums) >>> [1, 4, 2, 3]

ChD

over 4 years ago · Hanz Gallego Relatório

0

Esto se debe a que la evaluación, específicamente en el lado izquierdo de = , ocurre de izquierda a derecha:

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

Primero se asigna nums[i] , y luego ese valor se usa para determinar el índice en la asignación a nums[nums[i]-1]

Al hacer la tarea así:

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

... el índice de nums[nums[i]-1] depende del valor antiguo de nums[i] , ya que la asignación a nums[i] sigue después...

over 4 years ago · Hanz Gallego Relatório

0

De python.org

La asignación de un objeto a una lista de destino, opcionalmente entre paréntesis o corchetes, se define recursivamente de la siguiente manera.

...

  • De lo contrario: el objeto debe ser iterable con la misma cantidad de elementos que objetivos en la lista de objetivos, y los elementos se asignan, de izquierda a derecha, a los objetivos correspondientes.

Así que interpreto que eso significa que su tarea

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

es aproximadamente equivalente a

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

(con una mejor verificación de errores, por supuesto)

mientras que el otro

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

es como

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

Entonces, el lado derecho se evalúa primero en ambos casos. Pero luego las dos piezas del lado izquierdo se evalúan en orden y las asignaciones se realizan inmediatamente después de la evaluación. Crucialmente, esto significa que el segundo término en el lado izquierdo solo se evalúa después de que ya se haya realizado la primera asignación. Entonces, si actualiza nums[i] primero, entonces nums[nums[i] - 1] se refiere a un índice diferente que si actualiza nums[i] segundo lugar.

over 4 years ago · Hanz Gallego Relatório

0

Esto sucede de acuerdo con las reglas:

  • El lado derecho se evalúa primero.
  • Luego, cada valor del lado izquierdo obtiene su nuevo valor, de izquierda a derecha.

Entonces, con nums = [1, 2, 4, 3] , tu código en el primer caso

 nums[2], nums[nums[2]-1] = nums[nums[2]-1], nums[2]

es equivalente a:

 nums[2], nums[nums[2]-1] = nums[nums[2]-1], nums[2] nums[2], nums[nums[2]-1] = nums[3], nums[2] nums[2], nums[nums[2]-1] = 3, 4

y como ahora se evalúa el lado derecho, las asignaciones son equivalentes a:

 nums[2] = 3 nums[nums[2]-1] = 4 nums[2] = 3 nums[3-1] = 4 nums[2] = 3 nums[2] = 4

lo que da:

 print(nums) # [1, 2, 4, 3]

En el segundo caso, obtenemos:

 nums[nums[2]-1], nums[2] = nums[2], nums[nums[2]-1] nums[nums[2]-1], nums[2] = nums[2], nums[3] nums[nums[2]-1], nums[2] = 4, 3 nums[nums[2]-1] = 4 nums[2] = 3 nums[4-1] = 4 nums[2] = 3 nums[3] = 4 nums[2] = 3 print(nums) # [1, 2, 3, 4]
over 4 years ago · Hanz Gallego Relatório

0

En el lado izquierdo de su expresión, está leyendo y escribiendo números [i], no sé si Python garantiza el procesamiento de las operaciones de desempaquetado en orden de izquierda a derecha, pero supongamos que sí, su primer ejemplo sería equivalente a.

 t = nums[nums[i]-1], nums[i] # t = (3,4) nums[i] = t[0] # nums = [1,2,3,3] n = nums[i]-1 # n = 2 nums[n] = t[1] # nums = [1,2,4,3]

Mientras que su segundo ejemplo sería equivalente a

 t = nums[i], nums[nums[i]-1] # t = (4,3) n = nums[i]-1 # n = 3 nums[n] = t[0] # nums = [1,2,4,4] nums[i] = t[0] # nums = [1,2,3,4]

Lo cual es consistente con lo que tienes.

over 4 years ago · Hanz Gallego Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda