Chapter Summary

Chapter Summary

Key Points

  • 1.

    Type annotations are documentation that tools can check. Use built-in generics (list[float], dict[str, np.ndarray]), Union / | for alternatives, Literal for constrained strings, and TypeAlias for domain-specific names like ChannelMatrix. Annotations have no runtime cost and dramatically improve IDE support and error detection.

  • 2.

    Assert for invariants, raise for inputs. assert statements are for conditions that indicate bugs in your code — they are stripped by python -O. Use raise ValueError/TypeError for conditions caused by external input. The beartype library bridges the gap by enforcing type hints at runtime with O(1) overhead.

  • 3.

    Never compare floats with ==. Use np.testing.assert_allclose(a, b, atol=..., rtol=...) for numerical tests. The tolerance should reflect the condition number of the computation. Seed random number generators with np.random.default_rng(seed) (not the global np.random.seed) for reproducible stochastic tests.

  • 4.

    Profile before optimizing. Run python -m cProfile -s cumtime script.py to find actual bottlenecks. Use line_profiler for line-level timing and py-spy for production profiling with near-zero overhead. The bottleneck is rarely where intuition suggests.

  • 5.

    Package with pyproject.toml and the src layout. A single pyproject.toml replaces setup.py, setup.cfg, and requirements.txt. The src layout prevents import shadowing. Editable installs (pip install -e ".[dev]") enable rapid development. Entry points create installable CLI tools from your package.

Looking Ahead

With type-safe, tested, and properly packaged code, you are ready for the numerical computing chapters. Chapter 5 dives into NumPy's internals — memory layout, broadcasting rules, and advanced indexing — where type annotations and shape validation become indispensable for writing correct, high-performance array code.