text
| 1 | # Python Style Rules |
| 2 | |
| 3 | Ordered by how much damage breaking them does. |
| 4 | |
| 5 | ## Failures must be visible |
| 6 | |
| 7 | 1. **Never write a bare `except:` or `except Exception:` that continues.** It catches |
| 8 | the typo in the line above it too. Catch the specific exception you expect. |
| 9 | 2. **Never swallow an exception silently.** If ignoring it is right, log it or comment |
| 10 | why in one line. |
| 11 | 3. **Do not return `None` to signal an error** in a function that also returns `None` |
| 12 | legitimately. Raise, or return an explicit result type. |
| 13 | 4. **Chain exceptions.** `raise X from err`. Dropping the cause discards the traceback |
| 14 | that explains the failure. |
| 15 | 5. **Validate at the boundary.** Data from a network, a file, or a subprocess is |
| 16 | untrusted. Check it once on the way in, not defensively at every use. |
| 17 | |
| 18 | ## The obvious traps |
| 19 | |
| 20 | 6. **No mutable default arguments.** `def f(x=[])` shares one list across every call. |
| 21 | Use `None` and build inside. |
| 22 | 7. **Do not mutate a collection you are iterating.** Build a new one. |
| 23 | 8. **Close what you open.** Use `with`, always, including for subprocesses and locks. |
| 24 | 9. **Compare with `is` only for `None`, `True`, `False`.** Not for strings or numbers. |
| 25 | 10. **Beware truthiness on containers and numbers.** `if not count` is true for zero. |
| 26 | Say `if count is None` when that is what you mean. |
| 27 | |
| 28 | ## Types and data |
| 29 | |
| 30 | 11. **Type hints must be honest.** If it can return `None`, the hint says `| None`. |
| 31 | A wrong hint is worse than none, because tools trust it. |
| 32 | 12. **Prefer a dataclass or NamedTuple to a dict** for anything with a fixed shape. |
| 33 | A dict of known keys is a class that has not admitted it yet. |
| 34 | 13. **Do not use a tuple with more than three elements as a return value.** Name the |
| 35 | fields. |
| 36 | |
| 37 | ## Dependencies |
| 38 | |
| 39 | 14. **Do not add a dependency for something the standard library does.** `pathlib`, |
| 40 | `json`, `itertools`, `dataclasses`, `subprocess` cover an enormous amount. |
| 41 | 15. **Do not add a dependency for one function.** Copy the twenty lines, with credit. |
| 42 | 16. **Pin what you add**, and say in the commit why it is needed. |
| 43 | |
| 44 | ## Structure |
| 45 | |
| 46 | 17. **Functions do one thing and are named after it.** A name with `and` in it is two |
| 47 | functions. |
| 48 | 18. **No side effects at import time.** No network calls, no file writes, no |
| 49 | environment mutation in module scope. Import must be free. |
| 50 | 19. **Match the file you are editing.** Its conventions win over your preferences. |
| 51 | |
| 52 | ## Comments |
| 53 | |
| 54 | 20. **Comment the why.** The constraint, the workaround, the reason for the odd choice. |
| 55 | 21. **Docstring the contract**, not the implementation: what it takes, what it returns, |
| 56 | what it raises. |
| 57 | 22. **Delete commented-out code.** |
| 58 | |
| 59 | ## What good looks like |
| 60 | |
| 61 | ```python |
| 62 | # The vendor API returns 200 with an empty body on rate limit, so a status |
| 63 | # check is not enough to tell success from throttling here. |
| 64 | try: |
| 65 | payload = json.loads(response.text) |
| 66 | except json.JSONDecodeError as err: |
| 67 | raise UpstreamError(f"non-JSON body from {url}") from err |
| 68 | ``` |
| 69 | |
| 70 | Specific exception, cause preserved, and a comment explaining something the reader |
| 71 | could not have guessed from the code. |
| 72 |