Desempaquetado en Python: más allá de la asignación paralela

El desempaquetado usa * para asignar múltiples variables desde un solo iterable en una declaración de asignación. Esto hace que su código Python sea más limpio y rápido de escribir.

Introducción

Desempaquetar en Python se refiere a una operación que consiste en asignar un iterable de valores a una tupla (o list) de variables en una sola declaración de asignación. Como complemento, el término empaquetado se puede usar cuando recolectamos varios valores en una sola variable usando el operador iterable de desempaquetado, *.

Históricamente, los desarrolladores de Python se han referido genéricamente a este tipo de operación como desempaquetado de tuplas. Sin embargo, dado que esta característica de Python ha resultado ser bastante útil y popular, se ha generalizado a todo tipo de iterables. Hoy en día, un término más moderno y preciso sería desembalaje iterable.

En este tutorial, aprenderemos qué es el desempaquetado iterable y cómo podemos aprovechar esta característica de Python para hacer que nuestro código sea más legible, mantenible y pythonico.

Además, también cubriremos algunos ejemplos prácticos de cómo usar la función de desempaquetado iterable en el contexto de operaciones de asignación, bucles for, definiciones de funciones y llamadas a funciones.

Empaquetado y desempaquetado en Python {#empaquetado y desempaquetado en python}

Python permite que aparezca una tupla (o lista) de variables en el lado izquierdo de una operación de asignación. Cada variable en la tupla puede recibir un valor (o más, si usamos el operador *) de un iterable en el lado derecho de la asignación.

Por razones históricas, los desarrolladores de Python solían llamar a esto desempaquetado de tuplas. Sin embargo, dado que esta función se ha generalizado a todo tipo de iterables, un término más preciso sería desempaquetado iterable y así lo llamaremos en este tutorial.

Las operaciones de desempaquetado han sido bastante populares entre los desarrolladores de Python porque pueden hacer que nuestro código sea más legible y elegante. Echemos un vistazo más de cerca al desempaquetado en Python y veamos cómo esta función puede mejorar nuestro código.

Desempaquetando tuplas

En Python, podemos colocar una tupla de variables en el lado izquierdo de un operador de asignación (=) y una tupla de valores en el lado derecho. Los valores de la derecha se asignarán automáticamente a las variables de la izquierda según su posición en la tupla. Esto se conoce comúnmente como desempaquetado de tuplas en Python. Mira el siguiente ejemplo:

1
2
3
4
5
6
7
>>> (a, b, c) = (1, 2, 3)
>>> a
1
>>> b
2
>>> c
3

Cuando colocamos tuplas en ambos lados de un operador de asignación, se lleva a cabo una operación de desempaquetado de tuplas. Los valores de la derecha se asignan a las variables de la izquierda según su posición relativa en cada tupla. Como puede ver en el ejemplo anterior, a será 1, b será 2 y c será 3.

Para crear un objeto tupla, no necesitamos usar un par de paréntesis () como delimitadores. Esto también funciona para el desempaquetado de tuplas, por lo que las siguientes sintaxis son equivalentes:

1
2
3
>>> (a, b, c) = 1, 2, 3
>>> a, b, c = (1, 2, 3)
>>> a, b, c = 1, 2, 3

Dado que todas estas variaciones son sintaxis de Python válidas, podemos usar cualquiera de ellas, según la situación. Podría decirse que la última sintaxis se usa más comúnmente cuando se trata de desempaquetar en Python.

Cuando estamos desempaquetando valores en variables usando el desempaquetado de tuplas, el número de variables en la “tupla” del lado izquierdo debe coincidir exactamente con el número de valores en la “tupla” del lado derecho. De lo contrario, obtendremos un ValueError.

Por ejemplo, en el siguiente código, usamos dos variables a la izquierda y tres valores a la derecha. Esto generará un ValueError que nos dice que hay demasiados valores para desempaquetar:

1
2
3
4
>>> a, b = 1, 2, 3
Traceback (most recent call last):
  ...
ValueError: too many values to unpack (expected 2)

Nota: La única excepción a esto es cuando usamos el operador * para empaquetar varios valores en una variable como veremos más adelante.

Por otro lado, si usamos más variables que valores, obtendremos un ValueError pero esta vez el mensaje dice que no hay suficientes valores para desempaquetar:

1
2
3
4
>>> a, b, c = 1, 2
Traceback (most recent call last):
  ...
ValueError: not enough values to unpack (expected 3, got 2)

Si usamos un número diferente de variables y valores en una operación de desempaquetado de tuplas, obtendremos un ValueError. Eso es porque Python necesita saber sin ambigüedades qué valor entra en qué variable, para que pueda hacer la asignación en consecuencia.

Desempaquetando iterables

La función de desempaquetado de tuplas se hizo tan popular entre los desarrolladores de Python que la sintaxis se amplió para trabajar con cualquier objeto iterable. El único requisito es que el iterable produzca exactamente un elemento por variable en la ’tupla’ receptora (o ’lista’).

Consulte los siguientes ejemplos de cómo funciona el desempaquetado iterable en Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
>>> # Unpacking strings
>>> a, b, c = '123'
>>> a
'1'
>>> b
'2'
>>> c
'3'
>>> # Unpacking lists
>>> a, b, c = [1, 2, 3]
>>> a
1
>>> b
2
>>> c
3
>>> # Unpacking generators
>>> gen = (i ** 2 for i in range(3))
>>> a, b, c = gen
>>> a
0
>>> b
1
>>> c
4
>>> # Unpacking dictionaries (keys, values, and items)
>>> my_dict = {'one': 1, 'two':2, 'three': 3}
>>> a, b, c = my_dict  # Unpack keys
>>> a
'one'
>>> b
'two'
>>> c
'three'
>>> a, b, c = my_dict.values()  # Unpack values
>>> a
1
>>> b
2
>>> c
3
>>> a, b, c = my_dict.items()  # Unpacking key-value pairs
>>> a
('one', 1)
>>> b
('two', 2)
>>> c
('three', 3)

Cuando se trata de desempaquetar en Python, podemos usar cualquier iterable en el lado derecho del operador de asignación. El lado izquierdo se puede llenar con una ’tupla’ o con una ’lista’ de variables. Mira el siguiente ejemplo en el que usamos una tupla en el lado derecho de la instrucción de asignación:

1
2
3
4
5
6
7
>>> [a, b, c] = 1, 2, 3
>>> a
1
>>> b
2
>>> c
3

Funciona de la misma manera si usamos el iterador range():

1
2
3
4
5
6
7
>>> x, y, z = range(3)
>>> x
0
>>> y
1
>>> z
2

Aunque esta es una sintaxis de Python válida, no se usa comúnmente en el código real y tal vez sea un poco confuso para los desarrolladores principiantes de Python.

Finalmente, también podemos usar objetos establecer en operaciones de desempaquetado. Sin embargo, dado que los conjuntos son una colección desordenada, el orden de las asignaciones puede ser algo incoherente y puede dar lugar a errores sutiles. Mira el siguiente ejemplo:

1
2
3
4
5
6
7
>>> a, b, c = {'a', 'b', 'c'}
>>> a
'c'
>>> b
'b'
>>> c
'a'

Si usamos conjuntos en las operaciones de desempaquetado, entonces el orden final de las asignaciones puede ser bastante diferente de lo que queremos y esperamos. Por lo tanto, es mejor evitar el uso de conjuntos en las operaciones de desempaquetado, a menos que el orden de asignación no sea importante para nuestro código.

Embalaje con el operador *

El operador * se conoce, en este contexto, como el operador de desempaquetado de tupla (o iterable). Extiende la funcionalidad de desempaquetado para permitirnos recolectar o empaquetar múltiples valores en una sola variable. En el siguiente ejemplo, empaquetamos una ’tupla’ de valores en una sola variable usando el operador ‘*’:

1
2
3
>>> *a, = 1, 2
>>> a
[1, 2]

Para que este código funcione, el lado izquierdo de la asignación debe ser una tupla (o una lista). Es por eso que usamos una coma final. Esta tupla puede contener tantas variables como necesitemos. Sin embargo, solo puede contener una expresión destacada.

Podemos formar una expresión fija usando el operador de desempaquetado, *, junto con un identificador de Python válido, al igual que *a en el código anterior. El resto de las variables en la tupla del lado izquierdo se llaman variables obligatorias porque deben llenarse con valores concretos, de lo contrario, obtendremos un error. Así es como funciona esto en la práctica.

Empaquetando los valores finales en b:

1
2
3
4
5
>>> a, *b = 1, 2, 3
>>> a
1
>>> b
[2, 3]

Empaquetando los valores iniciales en a:

1
2
3
4
5
>>> *a, b = 1, 2, 3
>>> a
[1, 2]
>>> b
3

Empacando un valor en a porque b y c son obligatorios:

1
2
3
4
5
6
7
>>> *a, b, c = 1, 2, 3
>>> a
[1]
>>> b
2
>>> c
3

No empaquetar valores en a (a por defecto es []) porque b, c y d son obligatorios:

1
2
3
4
5
6
7
8
9
>>> *a, b, c, d = 1, 2, 3
>>> a
[]
>>> b
1
>>> c
2
>>> d
3

No proporciona ningún valor para una variable obligatoria (e), por lo que se produce un error:

1
2
3
>>> *a, b, c, d, e = 1, 2, 3
 ...
ValueError: not enough values to unpack (expected at least 4, got 3)

Empaquetar valores en una variable con el operador * puede ser útil cuando necesitamos recopilar los elementos de un generador en una sola variable sin usar la función list(). En los siguientes ejemplos, usamos el operador * para empaquetar los elementos de un generador de expresión y un rango objeto a una variable individual:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> gen = (2 ** x for x in range(10))
>>> gen
<generator object <genexpr> at 0x7f44613ebcf0>
>>> *g, = gen
>>> g
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
>>> ran = range(10)
>>> *r, = ran
>>> r
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

En estos ejemplos, el operador * empaqueta los elementos en gen y run en g y r respectivamente. Con su sintaxis, evitamos la necesidad de llamar a list() para crear una lista de valores a partir de un objeto rango, una expresión generadora o una función generadora.

Tenga en cuenta que no podemos usar el operador de desempaquetado, *, para empaquetar múltiples valores en una variable sin agregar una coma final a la variable en el lado izquierdo de la asignación. Entonces, el siguiente código no funcionará:

1
2
3
>>> *r = range(10)
  File "<input>", line 1
SyntaxError: starred assignment target must be in a list or tuple

Si tratamos de usar el operador * para empaquetar varios valores en una sola variable, entonces necesitamos usar la sintaxis tuple singleton. Por ejemplo, para que el ejemplo anterior funcione, solo necesitamos agregar una coma después de la variable r, como en *r, = range(10).

Uso del embalaje y desembalaje en la práctica {#uso del embalaje y desembalaje en la práctica}

Las operaciones de embalaje y desembalaje pueden ser muy útiles en la práctica. Pueden hacer que su código sea claro, legible y pythonico. Echemos un vistazo a algunos casos de uso comunes de empaquetado y desempaquetado en Python.

Asignación en paralelo

Uno de los casos de uso más comunes de desempaquetado en Python es lo que podemos llamar asignación paralela. La asignación en paralelo le permite asignar los valores en un iterable a una ’tupla’ (o ’lista’) de variables en una declaración única y elegante.

Por ejemplo, supongamos que tenemos una base de datos sobre los empleados de nuestra empresa y necesitamos asignar cada elemento de la lista a una variable descriptiva. Si ignoramos cómo funciona el desempaquetado iterable en Python, podemos escribir un código como este:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> employee = ["John Doe", "40", "Software Engineer"]
>>> name = employee[0]
>>> age = employee[1]
>>> job = employee[2]
>>> name
'John Doe'
>>> age
'40'
>>> job
'Software Engineer'

Aunque este código funciona, el manejo del índice puede ser complicado, difícil de escribir y confuso. Una solución pythonica, más legible y más limpia se puede codificar de la siguiente manera:

1
2
3
4
5
6
7
>>> name, age, job = ["John Doe", "40", "Software Engineer"]
>>> name
'John Doe'
>>> age
40
>>> job
'Software Engineer'

Usando el desempaquetado en Python, podemos resolver el problema del ejemplo anterior con una declaración única, sencilla y elegante. Este pequeño cambio haría que nuestro código fuera más fácil de leer y comprender para los desarrolladores novatos.

Intercambio de valores entre variables {#intercambio de valores entre variables}

Otra aplicación elegante de desempaquetado en Python es intercambiar valores entre variables sin usar una variable temporal o auxiliar. Por ejemplo, supongamos que necesitamos intercambiar los valores de dos variables a y b. Para hacer esto, podemos apegarnos a la solución tradicional y usar una variable temporal para almacenar el valor que se intercambiará de la siguiente manera:

1
2
3
4
5
6
7
8
9
>>> a = 100
>>> b = 200
>>> temp = a
>>> a = b
>>> b = temp
>>> a
200
>>> b
100

Este procedimiento toma tres pasos y una nueva variable temporal. Si usamos el desempaquetado en Python, podemos lograr el mismo resultado en un solo y conciso paso:

1
2
3
4
5
6
7
>>> a = 100
>>> b = 200
>>> a, b = b, a
>>> a
200
>>> b
100

En la instrucción a, b = b, a, estamos reasignando a a b y b a a en una línea de código. Esto es mucho más legible y directo. Además, tenga en cuenta que con esta técnica, no hay necesidad de una nueva variable temporal.

Recopilación de valores múltiples con * {#recopilación de valores múltiples con}

Cuando trabajamos con algunos algoritmos, puede haber situaciones en las que necesitemos dividir los valores de un iterable o una secuencia en fragmentos de valores para su posterior procesamiento. El siguiente ejemplo muestra cómo usar una lista y operaciones de corte para hacerlo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> seq = [1, 2, 3, 4]
>>> first, body, last = seq[0], seq[1:3], seq[-1]
>>> first, body, last
(1, [2, 3], 4)
>>> first
1
>>> body
[2, 3]
>>> last
4

Aunque este código funciona como esperábamos, trabajar con índices y sectores puede ser un poco molesto, difícil de leer y confuso para los principiantes. También tiene el inconveniente de hacer que el código sea rígido y difícil de mantener. En esta situación, el operador de desempaquetado iterable, *, y su capacidad de empaquetar varios valores en una sola variable pueden ser una gran herramienta. Echa un vistazo a esta refactorización del código anterior:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> seq = [1, 2, 3, 4]
>>> first, *body, last = seq
>>> first, body, last
(1, [2, 3], 4)
>>> first
1
>>> body
[2, 3]
>>> last
4

La línea first, *body, last = seq hace la magia aquí. El operador de desempaquetado iterable, *, recopila los elementos en medio de seq en body. Esto hace que nuestro código sea más legible, mantenible y flexible. Usted puede estar pensando, ¿por qué más flexible? Bueno, supongamos que seq cambia su longitud en el camino y aún necesita recopilar los elementos intermedios en body. En este caso, dado que usamos el desempaquetado en Python, no se necesitan cambios para que nuestro código funcione. Mira este ejemplo:

1
2
3
4
>>> seq = [1, 2, 3, 4, 5, 6]
>>> first, *body, last = seq
>>> first, body, last
(1, [2, 3, 4, 5], 6)

Si estuviéramos usando el corte de secuencia en lugar del desempaquetado iterable en Python, necesitaríamos actualizar nuestros índices y cortes para capturar correctamente los nuevos valores.

El uso del operador * para empaquetar varios valores en una sola variable se puede aplicar en una variedad de configuraciones, siempre que Python pueda determinar sin ambigüedades qué elemento (o elementos) asignar a cada variable. Echa un vistazo a los siguientes ejemplos:

1
2
3
4
5
6
7
8
9
>>> *head, a, b = range(5)
>>> head, a, b
([0, 1, 2], 3, 4)
>>> a, *body, b = range(5)
>>> a, body, b
(0, [1, 2, 3], 4)
>>> a, b, *tail = range(5)
>>> a, b, tail
(0, 1, [2, 3, 4])

Podemos mover el operador * en la tupla (o lista) de variables para recoger los valores según nuestras necesidades. La única condición es que Python pueda determinar a qué variable asignar cada valor.

Es importante tener en cuenta que no podemos usar más de una expresión fija en la tarea. Si lo hacemos, obtendremos un SyntaxError de la siguiente manera:

1
2
3
>>> *a, *b = range(5)
  File "<input>", line 1
SyntaxError: two starred expressions in assignment

Si usamos dos o más * en una expresión de asignación, obtendremos un SyntaxError que nos dice que se encontró la expresión con dos estrellas. Esto es así porque Python no puede determinar sin ambigüedades qué valor (o valores) queremos asignar a cada variable.

Eliminar valores innecesarios con * {#eliminar valores innecesarios con}

Otro caso de uso común del operador * es usarlo con un nombre de variable ficticio para descartar algunos valores inútiles o innecesarios. Mira el siguiente ejemplo:

1
2
3
4
5
6
7
>>> a, b, *_ = 1, 2, 0, 0, 0, 0
>>> a
1
>>> b
2
>>> _
[0, 0, 0, 0]

Para un ejemplo más perspicaz de este caso de uso, supongamos que estamos desarrollando un script que necesita determinar la versión de Python que estamos usando. Para hacer esto, podemos usar el atributo sys.version_info. Este atributo devuelve una tupla que contiene los cinco componentes del número de versión: principal, menor, ​​micro, releaselevel y serial. Pero solo necesitamos major, minor y micro para que nuestro script funcione, así que podemos descartar el resto. Aquí hay un ejemplo:

1
2
3
4
5
6
>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=8, micro=1, releaselevel='final', serial=0)
>>> mayor, minor, micro, *_ = sys.version_info
>>> mayor, minor, micro
(3, 8, 1)

Ahora, tenemos tres nuevas variables con la información que necesitamos. El resto de la información se almacena en la variable ficticia _, que nuestro programa puede ignorar. Esto puede dejar en claro a los desarrolladores recién llegados que no queremos (o no necesitamos) usar la información almacenada en _ porque este carácter no tiene un significado aparente.

Nota: De forma predeterminada, el intérprete de Python utiliza el carácter de subrayado _ para almacenar el valor resultante de las declaraciones que ejecutamos en una sesión interactiva. Entonces, en este contexto, el uso de este carácter para identificar variables ficticias puede ser ambiguo.

Devolución de tuplas en funciones

Las funciones de Python pueden devolver varios valores separados por comas. Dado que podemos definir objetos tupla sin usar paréntesis, este tipo de operación puede interpretarse como que devuelve una tupla de valores. Si codificamos una función que devuelve múltiples valores, entonces podemos realizar operaciones de empaquetado y desempaquetado iterables con los valores devueltos.

Mira el siguiente ejemplo en el que definimos una función para calcular el cuadrado y el cubo de un número dado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
>>> def powers(number):
...     return number, number ** 2, number ** 3
...
>>> # Packing returned values in a tuple
>>> result = powers(2)
>>> result
(2, 4, 8)
>>> # Unpacking returned values to multiple variables
>>> number, square, cube = powers(2)
>>> number
2
>>> square
4
>>> cube
8
>>> *_, cube = powers(2)
>>> cube
8

Si definimos una función que devuelve valores separados por comas, podemos realizar cualquier operación de empaquetado o desempaquetado de estos valores.

Fusión de iterables con el operador *

Otro caso de uso interesante para el operador de desempaquetado, *, es la capacidad de fusionar varios iterables en una secuencia final. Esta funcionalidad funciona para listas, tuplas y conjuntos. Echa un vistazo a los siguientes ejemplos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>>> my_tuple = (1, 2, 3)
>>> (0, *my_tuple, 4)
(0, 1, 2, 3, 4)
>>> my_list = [1, 2, 3]
>>> [0, *my_list, 4]
[0, 1, 2, 3, 4]
>>> my_set = {1, 2, 3}
>>> {0, *my_set, 4}
{0, 1, 2, 3, 4}
>>> [*my_set, *my_list, *my_tuple, *range(1, 4)]
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> my_str = "123"
>>> [*my_set, *my_list, *my_tuple, *range(1, 4), *my_str]
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, '1', '2', '3']

Podemos usar el operador de desempaquetado iterable, *, al definir secuencias para desempaquetar los elementos de una subsecuencia (o iterable) en la secuencia final. Esto nos permitirá crear secuencias sobre la marcha a partir de otras secuencias existentes sin llamar a métodos como append(), insert(), etc.

Los últimos dos ejemplos muestran que esta también es una forma más legible y eficiente de concatenar iterables. En lugar de escribir list(my_set) + my_list + list(my_tuple) + list(range(1, 4)) + list(my_str) simplemente escribimos [*my_set, *my_list, *my_tuple, *range(1, 4), *mi_cadena].

Desempaquetando diccionarios con el operador **

En el contexto de desempaquetado en Python, el operador ** se denomina [operador de desempaquetado de diccionario](https://docs.python.org/3/whatsnew/3.5.html#pep-448-additional-unpacking -generalizaciones). El uso de este operador fue ampliado por PEP 448. Ahora podemos usarlo en llamadas a funciones, en comprensiones y expresiones generadoras, y en muestra.

Un caso de uso básico para el operador de desempaquetado de diccionarios es fusionar múltiples diccionarios en un diccionario final con una sola expresión. Veamos cómo funciona esto:

1
2
3
4
5
>>> numbers = {"one": 1, "two": 2, "three": 3}
>>> letters = {"a": "A", "b": "B", "c": "C"}
>>> combination = {**numbers, **letters}
>>> combination
{'one': 1, 'two': 2, 'three': 3, 'a': 'A', 'b': 'B', 'c': 'C'}

Si usamos el operador de desempaquetado de diccionario dentro de una pantalla de diccionario, entonces podemos desempaquetar diccionarios y combinarlos para crear un diccionario final que incluya los pares clave-valor de los diccionarios originales, tal como lo hicimos en el código anterior.

Un punto importante a tener en cuenta es que, si los diccionarios que intentamos fusionar tienen claves repetidas o comunes, entonces los valores del diccionario más a la derecha anularán los valores del diccionario más a la izquierda. Aquí hay un ejemplo:

1
2
3
4
>>> letters = {"a": "A", "b": "B", "c": "C"}
>>> vowels = {"a": "a", "e": "e", "i": "i", "o": "o", "u": "u"}
>>> {**letters, **vowels}
{'a': 'a', 'b': 'B', 'c': 'C', 'e': 'e', 'i': 'i', 'o': 'o', 'u': 'u'}

Dado que la clave a está presente en ambos diccionarios, el valor que prevalece proviene de vocales, que es el diccionario más a la derecha. Esto sucede porque Python comienza a agregar los pares clave-valor de izquierda a derecha. Si, en el proceso, Python encuentra claves que ya existen, entonces el intérprete actualiza esas claves con el nuevo valor. Es por eso que el valor de la tecla a está en minúsculas en el ejemplo anterior.

Desempaquetado en For-Loops

También podemos usar el desempaquetado iterable en el contexto de bucles for. Cuando ejecutamos un bucle for, el bucle asigna un elemento de su iterable a la variable de destino en cada iteración. Si el elemento a asignar es iterable, entonces podemos usar una tupla de variables de destino. El bucle descomprimirá el iterable en cuestión en la tupla de las variables de destino.

Como ejemplo, supongamos que tenemos un archivo que contiene datos sobre las ventas de una empresa de la siguiente manera:

Producto Precio Unidades vendidas


Lápiz 0,25 1500 Cuaderno 1.30 550 Borrador 0.75 1000 ... ... ...

A partir de esta tabla, podemos construir una lista de tuplas de dos elementos. Cada tupla contendrá el nombre del producto, el precio y las unidades vendidas. Con esta información, queremos calcular los ingresos de cada producto. Para hacer esto, podemos usar un bucle for como este:

1
2
3
4
5
6
7
>>> sales = [("Pencil", 0.22, 1500), ("Notebook", 1.30, 550), ("Eraser", 0.75, 1000)]
>>> for item in sales:
...     print(f"Income for {item[0]} is: {item[1] * item[2]}")
...
Income for Pencil is: 330.0
Income for Notebook is: 715.0
Income for Eraser is: 750.0

Este código funciona como se esperaba. Sin embargo, estamos usando índices para obtener acceso a elementos individuales de cada tupla. Esto puede ser difícil de leer y comprender para los desarrolladores novatos.

Echemos un vistazo a una implementación alternativa utilizando el desempaquetado en Python:

1
2
3
4
5
6
>>> for product, price, sold_units in sales:
...     print(f"Income for {product} is: {price * sold_units}")
...
Income for Pencil is: 330.0
Income for Notebook is: 715.0
Income for Eraser is: 750.0

Ahora estamos usando el desempaquetado iterable en nuestro bucle for. Esto hace que nuestro código sea mucho más legible y fácil de mantener porque usamos nombres descriptivos para identificar los elementos de cada tupla. Este pequeño cambio permitirá que un desarrollador recién llegado comprenda rápidamente la lógica detrás del código.

También es posible utilizar el operador * en un bucle for para empaquetar varios elementos en una única variable de destino:

1
2
3
4
5
6
7
8
>>> for first, *rest in [(1, 2, 3), (4, 5, 6, 7)]:
...     print("First:", first)
...     print("Rest:", rest)
...
First: 1
Rest: [2, 3]
First: 4
Rest: [5, 6, 7]

En este ciclo for, capturamos el primer elemento de cada secuencia en first. Luego, el operador * captura una lista de valores en su variable de destino rest.

Finalmente, la estructura de las variables objetivo debe coincidir con la estructura del iterable. De lo contrario, obtendremos un error. Echa un vistazo al siguiente ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
>>> data = [((1, 2), 2), ((2, 3), 3)]
>>> for (a, b), c in data:
...     print(a, b, c)
...
1 2 2
2 3 3
>>> for a, b, c in data:
...     print(a, b, c)
...
Traceback (most recent call last):
  ...
ValueError: not enough values to unpack (expected 3, got 2)

En el primer ciclo, la estructura de las variables de destino, (a, b), c, concuerda con la estructura de los elementos en el iterable, ((1, 2), 2). En este caso, el ciclo funciona como se esperaba. Por el contrario, el segundo ciclo usa una estructura de variables de destino que no concuerda con la estructura de los elementos en el iterable, por lo que el ciclo falla y genera un ValueError.

Empaquetado y desempaquetado en funciones {#empaquetado y desempaquetado en funciones}

También podemos usar las funciones de empaquetado y desempaquetado de Python al definir y llamar funciones. Este es un caso de uso bastante útil y popular de empaquetar y desempaquetar en Python.

En esta sección, cubriremos los conceptos básicos de cómo usar el empaquetado y desempaquetado en las funciones de Python, ya sea en la definición de la función o en la llamada a la función.

Nota: Para obtener material más profundo y detallado sobre estos temas, consulte [Argumentos de longitud variable en Python con *args y **kwargs](/argumentos-de-longitud-variable-en-python -with -args-y-kwargs/).

Definición de funciones con * y **

Podemos usar los operadores * y ** en la firma de las funciones de Python. Esto nos permitirá llamar a la función con un número variable de argumentos posicionales (*) o con un número variable de argumentos de palabras clave, o ambos. Consideremos la siguiente función:

1
2
3
4
5
6
7
8
9
>>> def func(required, *args, **kwargs):
...     print(required)
...     print(args)
...     print(kwargs)
...
>>> func("Welcome to...", 1, 2, 3, site='wikihtp.com')
Welcome to...
(1, 2, 3)
{'site': 'wikihtp.com'}

La función anterior requiere al menos un argumento llamado requerido. También puede aceptar un número variable de argumentos posicionales y de palabras clave. En este caso, el operador * recopila o empaqueta argumentos posicionales adicionales en una tupla llamada args y el operador ** recopila o empaqueta argumentos de palabras clave adicionales en un diccionario llamado kwargs. Ambos, args y kwargs, son opcionales y automáticamente tienen por defecto () y {} respectivamente.

Aunque los nombres args y kwargs son ampliamente utilizados por la comunidad de Python, no son un requisito para que estas técnicas funcionen. La sintaxis solo requiere * o ** seguido de un identificador válido. Entonces, si puede dar nombres significativos a estos argumentos, hágalo. Eso sin duda mejorará la legibilidad de su código.

Funciones de llamada con * y ** {#funciones de llamada con y}

Al llamar a funciones, también podemos beneficiarnos del uso del operador * y ** para desempaquetar colecciones de argumentos en argumentos posicionales o de palabras clave separados, respectivamente. Esto es lo contrario de usar * y ** en la firma de una función. En la firma, los operadores significan recoger o empaquetar un número variable de argumentos en un identificador. En la llamada, quieren decir desempaquetar un iterable en varios argumentos.

Aquí hay un ejemplo básico de cómo funciona esto:

1
2
3
4
5
>>> def func(welcome, to, site):
...     print(welcome, to, site)
...
>>> func(*["Welcome", "to"], **{"site": 'wikihtp.com'})
Welcome to wikihtp.com

Aquí, el operador * desempaqueta secuencias como ["Bienvenido", "a"] en argumentos posicionales. De manera similar, el operador ** desempaqueta diccionarios en argumentos cuyos nombres coinciden con las claves del diccionario desempaquetado.

También podemos combinar esta técnica y la cubierta en la sección anterior para escribir funciones bastante flexibles. Aquí hay un ejemplo:

1
2
3
4
5
6
7
8
9
>>> def func(required, *args, **kwargs):
...     print(required)
...     print(args)
...     print(kwargs)
...
>>> func("Welcome to...", *(1, 2, 3), **{"site": 'wikihtp.com'})
Welcome to...
(1, 2, 3)
{'site': 'wikihtp.com'}

El uso de los operadores * y **, al definir y llamar a las funciones de Python, les otorgará capacidades adicionales y las hará más flexibles y potentes.

Conclusión

Desempaquetado iterable resulta ser una característica bastante útil y popular en Python. Esta característica nos permite descomprimir un iterable en varias variables. Por otro lado, el empaquetado consiste en capturar varios valores en una variable utilizando el operador de desempaquetado, *.

En este tutorial, aprendimos cómo usar el desempaquetado iterable en Python para escribir código más legible, mantenible y pythonic.

Con este conocimiento, ahora podemos usar el desempaquetado iterable en Python para resolver problemas comunes como la asignación paralela y el intercambio de valores entre variables. También podemos usar esta función de Python en otras estructuras como bucles for, llamadas a funciones y definiciones de funciones.

Licensed under CC BY-NC-SA 4.0