You’ve mastered the basics of classes and functions, but Python usually offers multiple ways to solve the same problem. Momentum slows down when you start overthinking:
- Should I pass this data around as a
dictor adataclass? - Do I use a list comprehension here, or a generator?
- Should I check if the key exists with
if, or just jump in withtry/except? - Should this be a
@staticmethod, or just a standalone function outside the class? - Should I write out all the parameters clearly, or just accept
**kwargs?
This post is Phase 2 of the repeatable mental checklist. Here are 5 more everyday Python design decisions with code examples.
1) Dictionary vs dataclass
Use a dict when:
- The keys are determined dynamically at runtime (e.g., aggregating data by dynamic user IDs).
- The data is a simple, unstructured payload passing through your system.
- You need to serialize it down to JSON immediately.
Use a dataclass when:
- You know the exact fields ahead of time (fixed schema).
- You want typo-protection (IDE autocomplete and type checkers like
mypy). - You need to attach behavior (methods) to the data later.
Example: The typo trap with dicts
| |
Example: Dataclasses give you safety
| |
2) List Comprehension (Eager) vs Generator (Lazy)
Use a List Comprehension [...] when:
- You need to know the length of the results (
len()). - You need to iterate over the collection multiple times.
- The dataset is small enough to fit comfortably in memory.
- You need to sort or slice the data from the end.
Use a Generator Expression (...) or yield when:
- You are dealing with a massive dataset (logs, large files, database cursors).
- You are chaining multiple processing steps together (pipelines).
- You might break out of the loop early (e.g., finding the first match).
Example: Don’t load the whole file into memory
| |
3) try/except (EAFP) vs if/else (LBYL)
Python embraces EAFP: “It’s Easier to Ask for Forgiveness than Permission.” Many other languages prefer LBYL: “Look Before You Leap.”
Use if/else (LBYL) when:
- The failure case is very common (e.g., 30% of the time). Exceptions are slow if they are raised constantly.
- Checking the condition is cheap and robust.
Use try/except (EAFP) when:
- The “happy path” happens 99% of the time.
- You want to avoid race conditions (e.g., a file is deleted after you check if it exists, but before you open it).
- The code is cleaner without deeply nested
ifstatements.
Example: Avoiding race conditions with EAFP
| |
4) @staticmethod vs Module-Level Function
Use a module-level function when:
- The function doesn’t need
selforcls. In Python, you don’t need to force everything into a class like you do in Java or C#.
Use @staticmethod when (Rarely):
- The function strictly belongs to the class namespace logically, and moving it outside would confuse the API.
- You are grouping it tightly with other specific class methods.
Example: Just use a function
| |
5) Explicit Parameters vs *args, **kwargs
Use explicit parameters when:
- You are building a public API, service class, or business logic core.
- Discoverability matters (other developers need to know exactly what to pass).
- You want type-checking and IDE support.
Use *args, **kwargs when:
- You are writing a wrapper, decorator, or middleware that passes arguments blindly to another function.
- You are subclassing and passing arguments up to
super().__init__().
Example: The frustration of hidden kwargs
| |
Example: The perfect use case for kwargs
| |
Compact decision cheat-sheet (Phase 2)
Dict vs Dataclass:
- Runtime keys & unstructured → Dict
- Fixed schema & IDE safety → Dataclass
List vs Generator:
- Need length & multiple passes → List
- Massive data & step-by-step pipelining → Generator
If/else vs Try/except:
- Common failure or cheap check → If/else
- Happy path 99% of the time or preventing race conditions → Try/except
Static method vs Function:
- Doesn’t need
selforcls→ Put it outside the class as Python function!
Explicit vs **kwargs:
- Business logic & APIs → Explicit parameters
- Decorators & wrappers →
*args, **kwargs
