Pythonic Patterns Highlights Summary
Every highlight bullet from the 11 pages in this section, gathered on one page and grouped by the page it came from.
- Duck typing means behavior is determined by what an object can do, not what it's declared to be
- The data model (dunder methods) is Python's mechanism for hooking into built-in syntax like with, for, and +
- Most 'pythonic patterns' are duck typing applied to a specific recurring problem, not independent tricks
- Classic Gang-of-Four patterns often collapse into a function or a protocol once duck typing is available
- Use EAFP to keep happy path unindented, default with dict get instead of catching broadly
- Context managers close files automatically, use pathlib for paths, specify encoding always
- Replace if elif chains with strategy dicts of callables, new formats add one entry
- Use factory functions to hide concrete class choices from callers
- Pass dependencies explicitly through constructors, avoid building IO clients inside methods
- Use sentinels with is comparison to distinguish unset values from None in APIs
- Python favors EAFP when success dominates and failures are rare exceptions
- Use EAFP when multiple failure modes collapse into one except block
- Use LBYL in hot loops to avoid exception setup cost from thousands of failures
- Avoid TOCTOU races by using try-except or dict.get instead of LBYL checks
- Catch specific exceptions, not bare except, to prevent hiding bugs
- EAFP keeps code linear; LBYL often duplicates work the operation does
- Context managers guarantee setup and teardown around code blocks even when exceptions fire
- Use with statement to acquire resources like files, sockets, locks, and DB connections
- The enter method returns the resource while exit cleans up after the block completes
- Contextmanager decorator splits setup before yield and teardown after yield in finally
- Returning False from exit propagates exceptions while True suppresses them after cleanup
- ExitStack composes multiple context managers without requiring deep nesting
- Replace growing if-elif chains with strategy dicts or singledispatch for runtime dispatch
- Strategy dicts handle string/enum keys while singledispatch handles type-based behavior
- Adding new dispatch variants requires new entries, not edits to the core dispatcher
- Wrap dict lookups with ValueError to replace vague KeyError messages with clear errors
- Singledispatch uses method resolution order to automatically select the most specific type
- Mutable default registries leak state across tests, copy them or use immutable mappings
- Factories decouple callers from concrete implementation details
- Builders assemble complex objects with many optional fields step by step
- Use factory for branching creation logic, builder for many optional fields
- Classmethods like from_dict or from_url are idiomatic Python factories
- Builder methods chain by returning self and validate at build completion
- Dataclasses with default_factory replace builders for simple cases
- Observer pattern maintains list of handlers, notify updates each on change
- Pub sub uses topic strings to decouple publishers from subscriber references
- Apply to domain events, UI decoupling, cross module hooks, async fan out
- Copy handler list during emit to prevent mutation during iteration bugs
- Wrap handler calls in try except to prevent one failure blocking others
- Unsubscribe on teardown to prevent memory leaks from forgotten subscriptions
- Dependency injection passes collaborators from outside instead of hardcoding construction
- Constructor injection passes dependencies into init and is the default Python pattern
- Inject dependencies for external I/O, testability, and environment specific configuration
- Protocol defines dependencies without inheritance, service never imports concrete implementations
- Composition root function or app factory builds the production dependency graph
- Too many constructor arguments signal missing facade, group dependencies in a dataclass
- Sentinels distinguish not-provided from None for optional fields with valid None values
- Null objects implement interfaces with no-op methods, eliminating is not None checks
- Never use mutable defaults like empty lists; use None or sentinels instead
- Identity checks with is are O(1) and unambiguous; avoid equality checks with equals
- Convert sentinels to null or omit keys at API boundaries since JSON cannot represent them
- Use enum members as named sentinels in public APIs to distinguish parameter defaults
- Mutable defaults share state across calls, use None and build fresh collections inside functions
- God objects mix every subsystem in one class, split by responsibility and inject collaborators
- Exception swallowing with bare except pass hides errors, catch specific exceptions and log context
- Mixed return types like dict None or False, avoid this with domain exceptions or Result types
- Premature abstraction creates interfaces for one implementation, apply the rule of three first
- Import-time side effects like database connections, move wiring to app factory or lifespan hooks
- Prefer EAFP with linear happy paths and specific exception handling over nested guards
- Inject dependencies through constructors and depend on protocols for testable swappable code
- Use dict dispatch with central registry and typed keys instead of long elif chains
- Never use mutable default arguments, use None sentinel and create fresh containers
- Replace boolean flag sprawl with enums or strategies to make modes explicit types
- Use with blocks to manage resource acquisition and release for files locks and transactions
Reviewed by Chris St. John·Last updated Jul 31, 2026