Python esencial · #11 de 11

Python esencial (avanzado)

Iteradores, generadores, excepciones, archivos, módulos

Por qué importa

Esta es la caja de herramientas del mundo real que vuelve productivo a Python a gran escala.

La idea

Los iterables te dejan transmitir datos de forma perezosa (lazy) (bueno para secuencias enormes / infinitas). Los generadores son la forma más barata de escribir un iterador. Las excepciones señalan los fallos sin dispersar comprobaciones de error. Los gestores de contexto (with) garantizan la limpieza.

Pruébalo

Generadores con yield: producen valores de forma perezosa:

def fib():
  a, b = 0, 1
  while True:
      yield a
      a, b = b, a + b

g = fib()
first_ten = [next(g) for _ in range(10)]
print(first_ten)
not loaded

Expresiones generadoras frente a comprensiones de lista: la misma sintaxis, evaluación perezosa:

# Total memory: O(1) for the generator, O(n) for the list
nums = range(1_000_000)
total = sum(x * x for x in nums if x % 3 == 0)
print("sum of squares of multiples of 3:", total)
not loaded

Excepciones + try/except/else/finally:

def safe_div(a, b):
  try:
      result = a / b
  except ZeroDivisionError:
      return None
  except TypeError as e:
      return f"type error: {e}"
  else:
      return result
  finally:
      # runs no matter what — log, close files, etc.
      pass

print(safe_div(10, 2))
print(safe_div(10, 0))
print(safe_div(10, "x"))
not loaded

Lo esencial de la biblioteca estándar

Rara vez necesitas escribir estructuras de datos desde cero: la biblioteca estándar tiene las versiones rápidas:

Anotaciones de tipo + dataclasses

Las anotaciones de tipo documentan la intención (y potencian a los editores / verificadores de tipos); @dataclass escribe por ti el código repetitivo de __init__ / __repr__:

from dataclasses import dataclass

def add(a: int, b: int) -> int:
    return a + b

@dataclass
class Point:
    x: int
    y: int

Verificación rápida

Mini ejercicios

Buenas y malas prácticas

Profundizando — iteración personalizada

Cualquier objeto que implemente __iter__ (devuelve el iterador) y __next__ (produce el siguiente valor o lanza StopIteration) funciona en un bucle for:

class CounterIter:
    def __init__(self, n):
        self.n = n
        self.i = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.i >= self.n:
            raise StopIteration
        val = self.i
        self.i += 1
        return val

Errores comunes

Conclusiones clave