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)
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)
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"))
Standard library essentials
You rarely need to write data structures from scratch — the standard library has the fast versions:
collections:deque,defaultdict,Counteritertools:accumulate,product,permutationsmath,statistics,random
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
- Q: Why use
with open(...)? A: It closes the file automatically.
Mini drills
- Write a generator that yields even numbers.
- Catch a
KeyErrorsafely. - Read a file and count its lines.
Do's and don'ts
- Do use generators for large data streams.
- Do catch specific exceptions.
- Don't leave files open.
- Don't swallow exceptions silently.
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 valCommon mistakes
- Mistake: Using bare
except:everywhere. Fix: Catch specific errors. - Mistake: Forgetting
yieldin a generator. Fix: Useyieldto stream values. - Mistake: Reading huge files with
read(). Fix: Iterate line by line.
Key takeaways
- Generators (
yield) build iterators with constant memory. - Use a generator expression
sum(x*x for x in nums)instead ofsum([x*x for x in nums])for large inputs. try/exceptfor control flow on EXPECTED failures; let unexpected errors bubble up.with open(path) as f:is the right idiom —f.close()is guaranteed.collections/itertoolsgive you fast, battle-tested building blocks.