ProgramaciónDesarrollador Backend

¿Cómo funciona la función incorporada zip() en Python, para qué se utiliza y cuáles son los detalles al procesar secuencias de diferente longitud?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Historia de la pregunta

La función zip() apareció por primera vez en Python 2 (entonces devolvía una lista), y a partir de Python 3 devuelve un iterador perezoso. "Une" varias secuencias en tuplas elemento por elemento, lo que ha hecho que el procesamiento de colecciones iterables paralelas sea cómodo y eficiente.

Problema

A menudo es necesario procesar varias listas (u otros tipos de secuencias) al mismo tiempo: por ejemplo, iterar sobre un par clave-valor o procesar coordenadas punto-par. La sincronización manual de índices es fuente de errores y dificulta la legibilidad del código, especialmente para colecciones de diferente longitud.

Solución

La función zip() acepta cualquier cantidad de objetos iterables y devuelve un iterador de tuplas, en cada uno de los cuales se toman los elementos correspondientes de cada iterable. Si las secuencias son de diferente longitud, el resultado se corta en la más corta.

Ejemplo de código:

names = ['Alice', 'Bob', 'Charlie'] ages = [24, 27, 30] for name, age in zip(names, ages): print(f'{name} tiene {age} años')

Se puede descomprimir zip usando *:

pairs = [(1, 'a'), (2, 'b'), (3, 'c')] nums, chars = zip(*pairs) print(nums) # (1, 2, 3) print(chars) # ('a', 'b', 'c')

Características clave:

  • zip() devuelve un iterador (en Python 3), no una lista.
  • El funcionamiento de zip() se interrumpe en el iterable más corto.
  • Permite el procesamiento paralelo de colecciones sin el control explícito de índices.

Preguntas engañosas.

¿Qué pasará si se pasa a zip() colecciones de diferente longitud?

zip() se detendrá cuando alcance el final de la colección más corta; los demás elementos de las colecciones largas se ignorarán.

print(list(zip([1,2,3], ['a','b']))) # [(1, 'a'), (2, 'b')]

¿Cómo obtener tuplas, completando las secuencias más cortas con un valor por defecto?

La zip() estándar no puede hacer esto, pero existe itertools.zip_longest para tal comportamiento:

from itertools import zip_longest for a, b in zip_longest([1,2], ['x','y','z'], fillvalue=None): print(a, b) # 1 x # 2 y # None z

¿Se puede "desempaquetar" el resultado de zip(), para volver a obtener las listas originales?

Sí, si todas las colecciones originales tenían la misma longitud y el resultado no ha sido alterado, el operador * permite descomprimir zip.

pairs = [(1,2), (3,4)] a, b = zip(*pairs) print(a) # (1, 3) print(b) # (2, 4)

Errores comunes y anti-patrones

  • Esperar que zip() siempre "llegue" al final de la colección más larga.
  • Suponer que en Python 3 zip() devuelve una lista (es un iterador, a veces es necesario envolverlo en list()).
  • Trabajar con zip en fuentes mutables que se consumen en cada iteración.

Ejemplo de la vida real

Caso negativo

Procesamiento de colecciones relacionadas de diferente longitud, sin tener en cuenta las particularidades de zip:

lst1 = [1,2,3,4] lst2 = ['a','b'] for x, y in zip(lst1, lst2): print(x, y) # 1 a # 2 b # (3,4) y 'c', 'd' de lst1 no se procesaron

Ventajas:

  • Simple y claro, si las secuencias son garantizadas de la misma longitud.

Desventajas:

  • Pérdida de valores, si la longitud real de las colecciones varía.

Caso positivo

Uso de zip_longest con fillvalue, para no perder ningún elemento:

from itertools import zip_longest lst1 = [1,2,3,4] lst2 = ['a','b'] for x, y in zip_longest(lst1, lst2, fillvalue='?'): print(x, y) # 1 a # 2 b # 3 ? # 4 ?

Ventajas:

  • Garantiza el procesamiento de todos los elementos.
  • Se puede definir explícitamente un valor "vacío".

Desventajas:

  • Necesita importar un módulo externo.
  • Es importante no olvidar el fillvalue, de lo contrario será None por defecto.