El desempaquetado se refiere al acto de extraer los elementos de una colección (como una list, tuple o dict) usando iteración.
Los valores desempaquetados se pueden asignar luego a variables dentro de la misma sentencia, algo que normalmente se conoce como asignación múltiple.
Los operadores especiales * y ** se usan a menudo en contextos de desempaquetado y con la asignación múltiple.
*<variable_name> y **<variable_name> no deben confundirse con * y **. Mientras que * y ** se usan para la multiplicación y la exponenciación respectivamente, *<variable_name> y **<variable_name> se usan como operadores de empaquetado y desempaquetado.
En la asignación múltiple, la cantidad de variables del lado izquierdo del operador de asignación (=) debe coincidir con la cantidad de valores del lado derecho.
Para separar los valores, usa una coma ,:
>>> a, b = 1, 2
>>> a
1
Si la asignación múltiple recibe una cantidad incorrecta de variables para los valores dados, se lanzará un ValueError:
>>> x, y, z = 1, 2
ValueError: too many values to unpack (expected 3, got 2)
La asignación múltiple no se limita a un solo tipo de datos:
>>> x, y, z = 1, "Hello", True
>>> x
1
>>> y
'Hello'
>>> z
True
La asignación múltiple se puede usar para intercambiar elementos en lists.
Esta práctica es bastante común en los algoritmos de ordenamiento.
Por ejemplo:
>>> numbers = [1, 2]
>>> numbers[0], numbers[1] = numbers[1], numbers[0]
>>> numbers
[2, 1]
Como los tuples son inmutables, no puedes intercambiar elementos en una tuple.
Los ejemplos a continuación usan lists, pero los mismos conceptos se aplican a los tuples.
En Python, es posible desempaquetar los elementos de list/tuple/dictionary en variables distintas.
Como los valores aparecen dentro de lists/tuples en un orden específico, se desempaquetan en variables en ese mismo orden:
>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"
Si hay valores que no necesitas, puedes usar _ para marcarlos:
>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"
Desempaquetar y asignar valores de un list/tuple que está dentro de un list o tuple (también conocidos como listas/tuplas anidadas) funciona igual que un desempaquetado superficial, aunque a menudo necesita calificadores para aclarar el contexto o la posición:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [c, d]] = fruits_vegetables
>>> a
"apple"
>>> d
"potato"
También puedes desempaquetar profundamente solo una parte de un list/tuple anidado:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [a, [c, d]] = fruits_vegetables
>>> a
["apple", "banana"]
>>> c
"carrot"
Si el desempaquetado tiene variables con una ubicación incorrecta o una cantidad incorrecta de valores, obtendrás un ValueError:
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables
ValueError: too many values to unpack (expected 1)
*
Cuando desempaquetas un list/tuple, puedes usar el operador * para capturar los valores «sobrantes».
Esto es más claro que rebanar el list/tuple (que en algunas situaciones es menos legible).
Por ejemplo, se puede extraer el primer elemento y luego colocar los valores restantes en un list nuevo sin el primer elemento:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *last = fruits
>>> x
"apple"
>>> last
["banana", "cherry", "orange", "kiwi", "melon", "mango"]
También podemos extraer los valores al principio y al final del list mientras agrupamos todos los valores del medio:
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"
>>> middle
["banana", "cherry", "orange", "kiwi"]
También podemos usar * en el desempaquetado profundo:
>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>>> [[a, *rest], b] = fruits_vegetables
>>> a
"apple"
>>> rest
["banana", "melon"]
Desempaquetar un diccionario es un poco diferente de desempaquetar un list/tuple.
La iteración sobre diccionarios usa las claves de forma predeterminada.
Así que al desempaquetar un dict, solo puedes desempaquetar las claves y no los valores:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory
>>> x
"apple"
Si quieres desempaquetar los valores, puedes usar el método <dict>.values():
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6
Si necesitas tanto las claves como los valores, usa el método <dict>.items().
<dict>.items() genera una vista iterable que contiene pares de clave-valor.
Estos se pueden desempaquetar en un tuple:
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.items()
>>> x
("apple", 6)
El empaquetado es la capacidad de agrupar varios valores en un solo list que se asigna a una variable.
Esto es útil cuando quieres desempaquetar valores, hacer cambios y luego empaquetar los resultados de nuevo en una variable.
También hace posible combinar 2 o más lists/tuples/dicts.
*
Empaquetar un list/tuple se puede hacer con el operador *.
Esto empaqueta todos los valores en un list/tuple.
>>> fruits = ("apple", "banana", "cherry")
>>> more_fruits = ["orange", "kiwi", "melon", "mango"]
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>>> combined_fruits = *fruits, *more_fruits
# If there is no * on to the left of the "=" the result is a tuple
>>> combined_fruits
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")
# If the * operator is used on the left side of "=" the result is a list.
# Note the trailing comma.
>>> *combined_fruits_too, = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
Para más información sobre el uso de * en el lado izquierdo, consulta PEP 3132.
**
Empaquetar un diccionario se hace con el operador **.
Esto empaqueta todos los pares clave-valor de un diccionario en otro diccionario, o combina dos diccionarios entre sí.
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
# then the pairs are packed into combined_fruits_inventory
>>> combined_fruits_inventory
{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
* y ** con funcionesCuando creas una función que acepta una cantidad arbitraria de argumentos, puedes usar *args o **kwargs en la definición de la función.
*args se usa para empaquetar una cantidad arbitraria de argumentos posicionales (sin palabra clave) como un tuple, y
**kwargs se usa para empaquetar una cantidad arbitraria de argumentos de palabra clave como un diccionario.
Uso de *args:
# This function is defined to take any number of positional arguments
>>> def my_function(*args):
... print(args)
# Arguments given to the function are packed into a tuple
>>> my_function(1, 2, 3)
(1, 2, 3)
>>> my_function("Hello")
("Hello")
>>> my_function(1, 2, 3, "Hello", "Mars")
(1, 2, 3, "Hello", "Mars")
Uso de **kwargs:
# This function is defined to take any number of keyword arguments
>>> def my_function(**kwargs):
... print(kwargs)
# Arguments given to the function are packed into a dictionary
>>> my_function(a=1, b=2, c=3)
{"a": 1, "b": 2, "c": 3}
*args y **kwargs también se pueden usar en combinación entre sí:
>>> def my_function(*args, **kwargs):
... print(sum(args))
... for key, value in kwargs.items():
... print(str(key) + " = " + str(value))
>>> my_function(1, 2, 3, a=1, b=2, c=3)
6
a = 1
b = 2
c = 3
También puedes escribir parámetros antes de *args para permitir argumentos posicionales específicos.
En ese caso, los argumentos de palabra clave individuales tienen que aparecer antes de **kwargs.
Los argumentos tienen que estar estructurados así:
def my_function(<positional_args>, *args, <key-word_args>, **kwargs)
Si no sigues este orden, obtendrás un error.
>>> def my_function(a, b, *args):
... print(a)
... print(b)
... print(args)
>>> my_function(1, 2, 3, 4, 5)
1
2
(3, 4, 5)
Escribir los argumentos en un orden incorrecto dará como resultado un error:
>>>def my_function(*args, a, b):
... print(args)
>>>my_function(1, 2, 3, 4, 5)
Traceback (most recent call last):
File "c:\something.py", line 3, in <module>
my_function(1, 2, 3, 4, 5)
TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'
Puedes usar * para desempaquetar un list/tuple de argumentos en una llamada a función.
Esto es muy útil para funciones que no aceptan un iterable:
>>> def my_function(a, b, c):
... print(c)
... print(b)
... print(a)
numbers = [1, 2, 3]
>>> my_function(*numbers)
3
2
1
Usar el desempaquetado con * junto con la función integrada zip() es otro caso de uso común.
La función zip() toma varios iterables y devuelve un list de tuples con los valores de cada iterable agrupados:
>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]
Tu amigo Linus es ingeniero de locomotoras y conduce trenes de carga entre ciudades. Aunque es increíble manejando trenes, no se le dan tan bien la logística ni las computadoras. Le gustaría contar con tu ayuda como programador para organizar los detalles de los trenes y corregir errores en los datos de las rutas.
Este ejercicio se podría resolver fácilmente usando slicing, indexación y varios métodos de dict.
Sin embargo, nos gustaría que practiques el empaquetado, el desempaquetado y la asignación múltiple al resolver cada una de las tareas a continuación.
Tu amigo ha estado llevando un registro del identificador (ID) de cada vagón, pero nunca está seguro de cuántos vagones tendrá que procesar el sistema en un momento dado.
Para el resto del programa de logística sería mucho más fácil tener estos datos empaquetados en un único list.
Implementa una función get_list_of_wagons() que acepte una cantidad arbitraria de IDs de vagones.
Cada ID será un número entero positivo.
La función debe entonces usar return para devolver los IDs dados como un único list.
>>> get_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
[1, 7, 12, 3, 14, 8, 5]
En este punto, ya empiezas a tener una idea de los datos y de cómo se usan en el programa de logística. El sistema de IDs siempre asigna a la locomotora el ID 1, y al resto de los vagones del tren les asigna un ID elegido al azar mayor que 1.
¡Tu amigo tuvo que conectar dos vagones nuevos al tren y olvidó actualizar el sistema!
Ahora, los dos primeros vagones del list del tren tienen que moverse al final, o todo quedará desordenado.
Para complicar más las cosas, tu amigo acaba de encontrar un segundo list que parece contener los IDs de vagones que faltan.
Lo único que recuerda es que, una vez que se muevan los vagones nuevos, los IDs de este segundo list deben colocarse justo después de la locomotora designada.
Linus te estaría muy agradecido si corriges sus errores y consolidas los datos.
Implementa una función fix_list_of_wagons() que reciba dos lists con IDs de vagones.
Debe reposicionar los dos primeros elementos del primer list al final, e insertar los valores del segundo list detrás (a la derecha) del ID de la locomotora (1).
La función debe entonces usar return para devolver un list con las modificaciones.
>>> fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15])
[1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5]
Ahora que todos los datos de los vagones son correctos, a Linus le gustaría que actualices la información de rutas del sistema.
A lo largo de una ruta de transporte, un tren puede hacer paradas en distintas estaciones para recoger o dejar carga.
Cada viaje puede tener una cantidad diferente de estos puntos intermedios de entrega.
Tu amigo quiere que actualices el dict de rutas del sistema con cualquier información de entrega que falte o sea adicional.
Implementa una función add_missing_stops() que acepte un dict de rutas seguido de una cantidad variable de argumentos de palabra clave.
Estos argumentos pueden tener la forma de un dict que contenga una o más paradas, o bien cualquier cantidad de pares de palabra clave stop_number=city.
Tu función debe entonces devolver el dict de rutas actualizado con una key adicional que contenga un list con todas las paradas agregadas, en orden.
>>> add_missing_stops({"from": "New York", "to": "Miami"},
stop_1="Washington, DC", stop_2="Charlotte", stop_3="Atlanta",
stop_4="Jacksonville", stop_5="Orlando")
{"from": "New York", "to": "Miami", "stops": ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"]}
Linus ha estado trabajando en el programa de rutas y ha notado que a ciertas rutas les faltan algunos detalles importantes.
La información inicial de la ruta se ha construido como un dict, y tu amigo quiere que actualices ese dict con todo lo que pueda faltar.
Cada ruta del sistema requiere detalles un poco distintos, así que Linus preferiría mucho una solución genérica.
Implementa una función llamada extend_route_information() que acepte dos dicts.
El primer dict contiene las ciudades de origen y destino entre las que va la ruta del tren.
El segundo dict contiene otros detalles de la ruta, como la velocidad, la longitud o la temperatura del tren.
La función debe devolver un dict consolidado con toda la información de la ruta.
El segundo dict puede contener propiedades diferentes o más de las que se muestran en el ejemplo.
>>> extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
{"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
Cuando Linus estaba inspeccionando el depósito de vagones, notó que los vagones no se estaban guardando en el orden correcto. Además de un ID, cada vagón tiene un color que corresponde al tipo de carga que lleva. Los vagones se guardan en el depósito en cuadrículas, donde cada columna de la cuadrícula tiene vagones del mismo color.
Sin embargo, el sistema de logística muestra que los lists de vagones que se van a guardar en el depósito tienen sus filas agrupadas por color.
Pero para que la cuadrícula de almacenamiento funcione correctamente, cada fila debe tener tres colores diferentes, de modo que las columnas queden alineadas por color.
Tu amigo quiere que ordenes los lists del depósito de vagones, para que los vagones se guarden correctamente.
Implementa una función llamada fix_wagon_depot() que acepte un list de tres elementos.
Cada elemento del list es una sublista (o «fila») que contiene tres tuples.
Cada tuple es un par (<wagon ID>, <wagon color>).
Tu función debe devolver un list con los tres lists de «fila» reordenados para que los vagones queden intercambiados en sus posiciones correctas.
>>> fix_wagon_depot([
[(2, "red"), (4, "red"), (8, "red")],
[(5, "blue"), (9, "blue"), (13,"blue")],
[(3, "orange"), (7, "orange"), (11, "orange")],
])
[
[(2, "red"), (5, "blue"), (3, "orange")],
[(4, "red"), (9, "blue"), (7, "orange")],
[(8, "red"), (13,"blue"), (11, "orange")]
]
Regístrate en Exercism para aprender y dominar Python con 17 conceptos146 ejercicios y mentoría humana real, todo gratis.