ProgramaciónDesarrollador Fullstack Python

Explique el mecanismo de funcionamiento de los operadores * y ** al desempacar secuencias y diccionarios en Python. ¿Cuáles son los matices y dónde es útil?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Historia de la pregunta:

Los operadores * y ** para desempacar aparecieron en Python hace mucho tiempo, pero su uso se ha ampliado con cada versión (por ejemplo, desde Python 3.5 se añadió soporte para combinar varias colecciones a través de * y **). Estos operadores hacen que trabajar con colecciones sea más flexible, incluso al pasar argumentos y recopilarlos en funciones.

Problema:

Sin desempaquetado, al trabajar con secuencias dinámicas, al pasar parámetros con un número variable de argumentos, surge la necesidad de escribir ciclos manualmente y verificar la dimensionalidad de las colecciones. Los errores pueden aparecer fácilmente con un uso incorrecto o si no se distingue la función de * y **.

Solución:

El operador * está destinado a desempaquetar secuencias (lista, tuple, conjunto), mientras que el operador ** se usa para desempacar diccionarios al llamar funciones o al combinar varios diccionarios. Permiten pasar argumentos de forma elegante, fusionar colecciones y convertir fácilmente estructuras arbitrarias en parámetros de función.

Ejemplo de código:

def foo(a, b, c): print(a, b, c) args = (1, 2, 3) foo(*args) # 1 2 3 params = {'a': 10, 'b': 20, 'c': 30} foo(**params) # 10 20 30 list1 = [1, 2] list2 = [3, 4] combined = [*list1, *list2] print(combined) # [1, 2, 3, 4]

Características clave:

  • *sequence pasa elementos como argumentos posicionales separados, **dict como argumentos nombrados.
  • Se pueden combinar y copiar colecciones de manera elegante usando [*a, *b] y {**d1, **d2}.
  • El desempacado se aplica tanto en la definición/declaración de funciones como en la llamada.

Preguntas trampa.

¿Se pueden usar * y ** para colecciones mixtas?

Al llamar a una función, * solo funciona con argumentos posicionales, y ** solo con argumentos nombrados. Si se pasa un diccionario no desempacado como * o una secuencia como **, se producirá un error.

def foo(a, b): print(a, b) foo(*{'a': 1, 'b': 2}) # Imprime: a b (¡claves del diccionario, no valores!)

**¿Qué pasa si los nombres de las claves se superponen al combinar diccionarios a través de {**d1, d2}?

El resultado será el valor del último diccionario con esa clave.

d1 = {'x': 1, 'y': 2} d2 = {'y': 33, 'z': 44} merged = {**d1, **d2} print(merged) # {'x': 1, 'y': 33, 'z': 44}

¿Se pueden usar * y ** dentro de comprensiones de listas o diccionarios?

Sí, esto es legal desde Python 3.5, por ejemplo:

lst = [1, 2, *range(3, 6)] # [1, 2, 3, 4, 5] dct = {**{'a': 1}, 'b': 2, **{'c': 3}}

Errores típicos y antipatrón

  • Pasar un diccionario a través de * (solo se desempacan las claves).
  • La superposición excesiva de argumentos nombrados provoca TypeError.
  • Intentar usar ** para un objeto que no es un mapeo.

Ejemplo de la vida real

Caso negativo

Un desarrollador acepta un diccionario en una función y lo descompone con * en lugar de **. Ocurre un comportamiento inesperado: en la función entran las claves, no los valores.

Ventajas:

El código no falla de inmediato, parece “funcionar”.

Desventajas:

Errores ocultos y discrepancia con la lógica esperada.

Caso positivo

Paso correcto de parámetros a través de **kwargs, fusión cuidadosa de diccionarios, uso de * en la combinación dinámica de secuencias.

Ventajas:

Máxima flexibilidad, concisión del código, facilidad de refactorización.

Desventajas:

Con un gran número de parámetros y colecciones, es importante prestar atención a los nombres y el orden, de lo contrario pueden surgir errores.