Core Python · #11 of 11

Python Core (Advanced)

Iterators, generators, exceptions, files, modules

Why it matters

This is the real-world toolbox that makes Python productive at scale.

The idea

Iterables let you stream data lazily (good for huge / infinite sequences). Generators are the cheapest way to write an iterator. Exceptions signal failure without scattering error checks. Context managers (with) guarantee cleanup.

Try it

Generators with yield — produce values lazily:

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

Generator expressions vs list comprehensions — same syntax, lazy evaluation:

# 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

Exceptions + 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

Standard library essentials

You rarely need to write data structures from scratch — the standard library has the fast versions:

Typing hints + dataclasses

Type hints document intent (and power editors / type checkers); @dataclass writes the boilerplate __init__ / __repr__ for you:

from dataclasses import dataclass

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

@dataclass
class Point:
    x: int
    y: int

Quick check

Mini drills

Do's and don'ts

Going deeper — custom iteration

Any object that implements __iter__ (returns the iterator) and __next__ (yields the next value or raises StopIteration) works in a for loop:

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

Common mistakes

Key takeaways