In this article I will give you 10 tips for cleaner code. Hope this helps improve your Python code.
1. Smaller units.
Break long functions (methods) into multiple smaller ones that do one thing (SRP or “Single-responsibility principle”). This will make your code more modular and easier to test.
For example, a function that parses a csv file, builds up a result list and prints the results does 3 things and should be split accordingly. For more about writing better functions in Python, see this article.
Update: a great addition by Michael Kennedy is to watch for comments in long functions, usually those hint at the need of making more smaller functions using the extract method refactoring.
2. Magic numbers.
Move “magic numbers” sprinkled in your code to constants (at the top of your module) or function parameters. This will make your code more readable and easier to change.
3. Global scope.
Watch out for anything that you put in the global scope, localize variables (data) as much as possible to avoid side effects. You might do this using dedicated modules, functions or classes. Reference: When to write classes and why it matters.
4. Linters.
Use flake8 + black (an alternative is blue). This leads to more consistent (PEP8 compliant) code which is easier to read. Also, there are a ton of flake8 plugins that can boost the quality of your code.
Remember that as a developer you write code much more for the next human (which often is you) than for the machine.
For automating this look into the excellent pre-commit tool. Here is a video we did on how to set it up with the common Python tools.
5. Narrow blocks.
Keep try/except blocks narrow (ask yourself: “Are all those lines really going to throw this exception?”). Avoid bare exceptions or just using pass
or re-raising an exception without additional error handling (e.g., logging) code. Here are some more tips as well to improve your error handling.
Instrumenting is also important to be able to diagnose bugs in production! We spoke about this with Thomas Gaigher on our podcast, you can listen to the interview here.
6. Idiomatic code.
Leverage the full Python feature set, for example replace a try/finally with a with statement (context manager) or don’t overly check conditions (leaping), just “ask for forgiveness”.
Another example is relying on Python’s concept of truthiness (e.g., just do if my_list
instead of if len(my_list) > 0)
, although some people pointed out that the latter is more explicit.
A must watch is Raymond Hettinger’s talk Transforming code into Beautiful, idiomatic Python (our notes).
Using the common Python patterns leads to shorter, more readable, more performant and more maintainable code.
7. Use the right data structures.
For example, if you check for membership in a big collection, it’s more performant to use a dict
/ set
over a list, because the former uses hash tables so have O(1)
lookup performance (see our performance article).
Or what if you have to make a lot of inserts and deletes on both ends of a sequence? You can use a (collections) deque over a list.
Another example is the bisect
module in the Standard Library which provides functions to support maintaining order upon insert. As per the docs this can improve performance for long lists of items with expensive comparison operations (e.g. repeatedly sorting a list).
8. Use the Standard Library.
You don’t have to reinvent the wheel. For example, you can use (collections) Counter to get most common items.
Do you want caching, functools’ lru_cache()
has you covered (cache()
since 3.9).
And get familiar with the Python built-ins functions (e.g., all()
, any()
, sorted()
, enumerate()
, zip()
), they are excellent functions that lead to concise and more performant code. We made an overview video you can watch here.
9. Use a mapping.
Long if-elif-elif-elif-else
‘s are quite ugly and hard to maintain. You can beautifully refactor those using a mapping (dict
). Less lines of code, easier to maintain.
Since 3.10 you can also use “Structural Pattern Matching” (match / case – PEP 634).
10. Flat is better than nested (Zen of Python).
If you have a lot of nested code (the infamous “arrow shape”), it’s time to pause and reflect. Code with high cyclomatic complexity is hard to maintain. This goes back to 1. – write smaller units.
Flake8 has a max-complexity switch to show which parts of your code are overly complex.
Lastly, apart from the Python a lot of this is related to general software developer best practices.
One important book I recommend every Python developer to read is the classic Refactoring (2nd ed) book by Martin Fowler. It talks about the most common and important refactorings that will make your code better.
– Bob
Update 13th of Feb 2023: I discussed the clean code lessons from this article with Michael Kennedy on his Talk Python podcast:
I hope these tips are useful and make you think about your code. We’re all about implementation though so a lot of this only comes to the surface when you work on bigger projects.
We’ve created a safe and inclusive environment where people code real world projects getting their code reviewed by expert coaches. The insights they gain through this process makes them feel as real developers and their skill set rapidly matures.
If you’re interested to get past tutorial hell, gain confidence in your skills (taming the imposter syndrome animal), then check out our PDM program.
We think it’s one of the most effective (and engaging) ways to learn fundamental (and advanced) developer skills in a short period of time. You fix the common “you don’t know what you don’t know” gap and you’ll come out with a better portfolio containing tangible projects.
Lastly, the mindset side will also give you a new, and often unexpected focus on your career.