Tutorials
Practical tutorials for the work we actually do. Pick one, read it in order, and apply it.
A decorator is just a function that wraps another function. Build a real timing decorator step by step: functions as values, the wrapper, the @ syntax, and functools.wraps. After that, the syntax stops looking like magic.
Reading a big file into a list can kill your program. A generator handles one line at a time. See the memory difference measured on a real file, learn what yield does, and chain small generators into a pipeline.
Python ignores type hints when it runs. A checker like mypy doesn't. See one file crash on its first bug, then see mypy find both bugs before anything runs, and learn where to start adding hints in an existing project.
asyncio.gather runs many waiting tasks at once in one thread and returns results in order. Avoid await in a loop and blocking calls, keep failures with return_exceptions=True, and limit concurrency with a Semaphore.
Catch a missing package resource by inspecting a wheel and testing its import, version, data file and console command in a clean environment.
A closure remembers variables from the function that created it. Build separate counters with no globals, fix the UnboundLocalError with nonlocal, avoid the lambda-in-a-loop trap, and know when a class is clearer.
Don't guess which part is slow. Run cProfile, read cumtime and tottime the right way, fix the one line that matters, and measure again. In the example, the obvious suspect isn't the problem.
A for loop calls iter() once and next() until StopIteration. Write __iter__ and __next__ yourself, avoid the iterator that only works once, and know when a generator in __iter__ does the same job in fewer lines.
Start mypy with its defaults, fix what it finds, turn on check_untyped_defs, then make one module at a time strict. Every function you annotate checks every place that calls it, so start with the busiest ones.
Downloads spend most of their time waiting. ThreadPoolExecutor lets many waits overlap: 20 half-second downloads in 2 seconds instead of 10. Handle failures with as_completed, pick a sensible worker count and respect the server.
Write a context manager when something must be undone no matter how the block ends. Build one as a class and with @contextmanager, keep yield inside try/finally, and don't swallow errors by accident.
Keep a counter's value in a closure so nothing outside can set it to -5 or "abc". The pattern, how it compares with a class with