¿Qué son las expresiones de asignación (usando el operador “walrus” o “:=”)? ¿Por qué se agregó esta sintaxis?

7 minutos de lectura

Avatar de usuario de Chris_Rands
Chris_Rands

Desde Python 3.8, el código puede usar el llamado operador “morsa” (:=), documentado en PEP 572para expresiones de asignación.

Esto parece una nueva característica realmente sustancial, ya que permite esta forma de asignación dentro de las comprensiones y lambdas.

¿Cuáles son exactamente las especificaciones sintácticas, semánticas y gramaticales de las expresiones de asignación?

¿Por qué se introdujo este concepto nuevo (y aparentemente bastante radical), a pesar de que PEP 379 (que propone la idea similar de “Agregar una expresión de asignación”) fue retirado?

  • ¿Hay preguntas formuladas orgánicamente sobre este tema que se puedan cerrar con un enlace a esta pregunta de referencia? Una pregunta que, de lo contrario, podría estar acercándose a “demasiado amplia” ciertamente puede ser justificable cuando aborda lo que de otro modo es una fuente de duplicados comunes.

    – Charles Duffy

    11 mayo 2018 a las 17:53

  • Esto debe ser reabierto. Esto definitivamente no es “demasiado amplio”. Es un tema muy específico y una muy buena pregunta de referencia.

    – Panagiotis Kanavos

    5 oct 2018 a las 16:27

  • Si bien no debe tomarse demasiado literalmente porque estoy seguro de que Python puede diferir de alguna manera, esta es una de las mejores características de Go y hay ejemplos a lo largo de los documentos de Go

    – Brad Salomón

    7 abr. 2019 a las 12:54

  • Solo para darle una perspectiva histórica: una larga y acalorada discusión entre los desarrolladores de Python precedió a la aprobación de PEP 572. Y parece ser una de las razones por qué Guido renunció como BDFL. Las expresiones de asignación tienen una serie de casos de uso válidos, pero también se pueden usar de forma incorrecta fácilmente para hacer que el código sea menos legible. Trate de limitar el uso del operador morsa para limpiar los casos que reducen la complejidad y mejoran la legibilidad.

    – Jeyekomon

    16 oct 2019 a las 11:10

  • Por lo que vale: los primeros borradores de PEP 572 propusieron el mismo tipo de as sintaxis como en el PEP 379. Aunque hubo mucha discusión y una gran cantidad de trabajo realizado en el PEP, fueron solo unos cuatro meses y medio desde la propuesta hasta la aceptación; algunos otros PEP han tomado años.

    – Karl Knechtel

    hace 3 horas

Avatar de usuario de Chris_Rands
Chris_Rands

PEP 572 contiene muchos de los detalles, especialmente para la primera pregunta. Trataré de resumir/citar de manera concisa algunas de las partes más importantes del PEP:

Razón fundamental

Permitir esta forma de asignación dentro de las comprensiones, como listas de comprensión y funciones lambda donde las asignaciones tradicionales están prohibidas. Esto también puede facilitar la depuración interactiva sin necesidad de refactorizar el código.

Ejemplos de casos de uso recomendados

a) Obtener valores condicionales

por ejemplo (en Python 3):

command = input("> ")
while command != "quit":
    print("You entered:", command)
    command = input("> ")

puede llegar a ser:

while (command := input("> ")) != "quit":
    print("You entered:", command)

Del mismo modo, desde los documentos:

En este ejemplo, la expresión de asignación ayuda a evitar llamar a len() dos veces:

if (n := len(a)) > 10:
    print(f"List is too long ({n} elements, expected <= 10)")

b) Simplificación de comprensiones de listas

Por ejemplo:

stuff = [(lambda y: [y,x/y])(f(x)) for x in range(5)]

puede llegar a ser:

stuff = [[y := f(x), x/y] for x in range(5)]

Sintaxis y semántica

En cualquier contexto donde se puedan usar expresiones arbitrarias de Python, un expresión nombrada puede aparecer Esta es de la forma name := expr dónde expr es cualquier expresión de Python válida y el nombre es un identificador.

El valor de dicha expresión nombrada es el mismo que el de la expresión incorporada, con el efecto secundario adicional de que al objetivo se le asigna ese valor

Diferencias con declaraciones de asignación regulares

Además de ser una expresión en lugar de una declaración, hay varias diferencias mencionadas en el PEP: las asignaciones de expresiones van de derecha a izquierda, tienen diferente prioridad alrededor de las comas y no admiten:

  • Múltiples objetivos
x = y = z = 0  # Equivalent: (z := (y := (x := 0)))
  • Asignaciones no a un solo nombre:
# No equivalent
a[i] = x
self.rest = []
  • Empaquetado/desempaquetado iterable
# Equivalent needs extra parentheses

loc = x, y  # Use (loc := (x, y))
info = name, phone, *rest  # Use (info := (name, phone, *rest))

# No equivalent

px, py, pz = position
name, phone, email, *other_info = contact
  • Anotaciones de tipo en línea:
# Closest equivalent is "p: Optional[int]" as a separate declaration
p: Optional[int] = None
  • La asignación aumentada no es compatible:
total += tax  # Equivalent: (total := total + tax)

  • Estoy probando esto ahora mismo y no es la única diferencia.

    – Adelín

    19 de julio de 2018 a las 8:29

  • hay toda una lista de diferencias entre = y :=de los cuales solo uno se enumera aquí

    – Adelín

    19 de julio de 2018 a las 8:38

  • Mi punto es que esto podría ser una muy buena pregunta y respuesta canónica, pero necesita un pulido serio.

    – Adelín

    19 de julio de 2018 a las 8:39

  • @Adelin Me pregunto cómo lo está probando si la versión alfa se lanzará en 2019.

    usuario4396006

    30 de agosto de 2018 a las 16:48

  • @JC – Uno puede construir desde el fuente en cualquier momento para probar la última versión de Python. Así es como lo hacen los desarrolladores de Python, pero cualquiera que tenga curiosidad también podría hacer lo mismo

    – Nathan

    21 de mayo de 2019 a las 17:46

Avatar de usuario de Jonathon Reinhart
jonathan reinhart

Un par de mis ejemplos favoritos de cómo las expresiones de asignación pueden hacer que el código sea más conciso y fácil de leer:

if declaración

Antes:

match = pattern.match(line)
if match:
    return match.group(1)

Después:

if match := pattern.match(line):
    return match.group(1)

Infinito while declaración

Antes:

while True:
    data = f.read(1024)
    if not data:
        break
    use(data)

Después:

while data := f.read(1024):
    use(data)

Hay otros buenos ejemplos en el PEP.

  • Ejemplos especialmente buenos: muestran un aspecto bajo el cual C a veces lograba ser más claro y elegante que Python, pensé que nunca iban a arreglar esto

    – Aquila Irreale

    15 de agosto de 2019 a las 19:21

Algunos ejemplos más y razones ahora que 3.8 ha sido lanzado oficialmente.

Nombrar el resultado de una expresión es una parte importante de la programación, ya que permite usar un nombre descriptivo en lugar de una expresión más larga y permite su reutilización. Actualmente, esta función solo está disponible en forma de declaración, por lo que no está disponible en comprensiones de lista y otros contextos de expresión.

Fuente: Comentario de reddit de LicensedProfessional

Manejar una expresión regular coincidente

if (match := pattern.search(data)) is not None:
    # Do something with match

Un bucle que no se puede reescribir trivialmente usando 2-arg iter()

while chunk := file.read(8192):
   process(chunk)

Reutilizar un valor que es costoso de calcular

[y := f(x), y**2, y**3]

Comparta una subexpresión entre una cláusula de filtro de comprensión y su salida

filtered_data = [y for x in data if (y := f(x)) is not None]

¿Qué es el operador :=?

En lenguaje sencillo := es un expresión + asignación operador. ejecuta una expresión y asigna el resultado de esa expresión en una sola variable.

¿Por qué se necesita el operador :=?

El caso útil simple será reducir las llamadas a funciones en las comprensiones mientras se mantiene la redabilidad.

Consideremos una lista de comprensión para agregar uno y filtrar si el resultado es mayor que 0 sin un operador :=. Aquí necesitamos llamar a la función add_one dos veces.

[add_one(num) for num in numbers if add_one(num) > 0]

Caso 1:

def add_one(num):
    return num + 1

numbers = [1,2,3,4,-2,45,6]


result1 = [value for num in numbers if (value := add_one(num)) > 0]
>>> result1
[2, 3, 4, 5, 46, 7]

El resultado es el esperado y no necesitamos llamar a la función add_one para llamar dos veces, lo que muestra la ventaja de := operador

ser cauteloso con Walaro := operador mientras usa la comprensión de lista

Los siguientes casos pueden ayudarlo a comprender mejor el uso de := operador

Caso 2:

def add_one(num):
    return num + 1

numbers = [1,2,3,4,-2,45,6]

>>> result2 = [(value := add_one(num)) for num in numbers if value > 0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <listcomp>
NameError: name 'value' is not defined

Caso 3: cuando una variable global se establece en positivo

def add_one(num):
    return num + 1

numbers = [1,2,3,4,-2,45,6]

value = 1

result3 = [(value := add_one(num)) for num in numbers if value > 0]
>>> result3
[2, 3, 4, 5, -1]

Caso 4: cuando una variable global se establece en negativo

def add_one(num):
    return num + 1

numbers = [1,2,3,4,-2,45,6]

value = -1

result4 = [(value := add_one(num)) for num in numbers if value > 0]
>>> result4
[]

¿Ha sido útil esta solución?