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 lambda
s.
¿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?
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óndeexpr
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
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)
-
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
[]
¿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