13 September 2023
Writing clean, readable and maintainable code is crucial for any Python developer. Clean code directly impacts the quality, sustainability and extendibility of a software project. This comprehensive guide explores various techniques, best practices and tools to help Python developers write cleaner production-grade code.
This article is part of our support resources for our Clean Code with Python Course.
Clean code refers to code that is simple, easy to understand and maintain. The hallmarks of clean code include:
Writing clean code is critical because:
Follow these core principles to write cleaner and more Pythonic code:
Use readable variable and function names that describe purpose and functionality:
# Good
def calculate_total_price(base_price, tax_rate):
# logic
# Not as good
def calc(p, r):
# logic
Avoid single letter names except common idioms like x, y, i
for iterators.
Break code into logical modules, classes and functions based on single responsibilities. For example, separate out:
This improves reusability and simplifies testing.
Keep code simple by limiting complexity:
Complex code is harder to understand and maintain.
Utilise built-in Python features instead of reinventing the wheel:
dict
, set
over custom typeswith
statements for resource managementThis results in more idiomatic and cleaner code.
Eliminate code duplication by abstracting common functionality into reusable functions/classes. For example:
# Bad
print_report(report1)
print_report(report2)
# Good
def print_report(report):
# reusable report printing code
print_report(report1)
print_report(report2)
Comments should explain logic and intent, not just describe code:
# Bad
x = x + 1 # Increment x
# Good
x = x + 1 # Compensate for border pixel during cropping
Avoid obvious comments that duplicate functionality already evident from the code.
Follow common Python best practices and idioms:
with
for resource handlingThis helps experienced Python developers easily understand code.
Let us explore some actionable techniques to improve cleanliness and quality of Python code:
PEP8 is the official Python style guide. Some key points:
lower_case_names
Consistent styling makes code more readable.
Linters like pycodestyle
, pylint
and flake8
help enforce styles and find issues:
Fix linter issues immediately to avoid accumulation of problems.
Auto-formatters like black
and isort
format code to follow standard styles:
# Before black
import numpy as np
def calculate_stats(arr):
mean = np.mean(arr)
std_dev = np.std(arr)
return mean, std_dev
# After black
import numpy as np
def calculate_stats(arr):
mean = np.mean(arr)
std_dev = np.std(arr)
return mean, std_dev
Consistent styling enhances readability.
Write docstrings to document modules, classes, and functions:
def calculate_total(amount, tax_rate, discount):
"""Calculates total with tax and discount.
Args:
amount: Total amount before tax and discount
tax_rate: Tax rate as percentage
discount: Discount rate as percentage
Returns:
Total amount after applying tax and discount.
"""
This provides a built-in way to document code right next to implementation.
Type hints clearly specify input and output types:
def process_data(inputs: List[int]) -> Dict[str, float]:
# Implementation here
This allows type checking and improves understandability.
Use descriptive names and follow conventions:
lower_case_underscored
UpperCamelCase
UPPER_CASE_UNDERSCORED
lower_case_underscored
@classmethod
lower_case_underscored
__dunder_method__
Functions with 3 or fewer parameters are ideal. Avoid functions with more than 5 parameters.
Long parameter lists increase complexity and are hard to use properly.
Use context managers to manage resources like files/network connections:
with open('file.txt') as f:
data = f.read()
# File closed automatically when out of scope
This prevents resource leaks and hides error handling.
Write unit tests covering various use cases to catch bugs and prevent regressions:
# test_splitter.py
import unittest
from splitter import split_list
class TestSplitter(unittest.TestCase):
def test_basic_split(self):
result = split_list([1,2,3])
self.assertEqual(result, [[1],[2],[3]])
def test_empty_list(self):
result = split_list([])
self.assertEqual(result, [])
if __name__ == '__main__':
unittest.main()
Aim for test coverage of all critical parts of the code.
Handle exceptions gracefully and provide context:
try:
process_data(inputs)
except ValueError as e:
print(f"Inputs are invalid: {e}")
This makes errors easier to understand and debug.
Eliminating code duplication through refactoring is key. For example:
# Bad
process_x(x)
process_y(y)
# Good
def process(data):
# reused processing logic
process(x)
process(y)
Refactoring improves maintainability and modularity.
Use Git & GitHub for:
This enables a streamlined development workflow.
Tools like Jenkins and GitHub Actions enable automating:
This automates quality control and delivery pipeline.
PEP20 describes guiding principles for writing Pythonic code:
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Internalizing these principles helps in writing more idiomatic Python code.
Here are some useful code formatting tools:
Tool | Purpose |
---|---|
Black | Opinionated formatter following PEP8 |
isort | Sorts imports alphabetically |
autopep8 | Automatically formats to PEP8 style |
These tools help uniformly format code and make it more readable.
These linting tools identify issues:
Tool | Focus |
---|---|
pycodestyle | PEP8 style violations |
pyflakes | Basic static analysis |
pylint | Errors, anti-patterns, complexity |
flake8 | Combines pyflakes, pycodestyle, mccabe |
Fixing linter issues improves overall quality.
Testing tools to validate code:
Tool | Description |
---|---|
unittest | Inbuilt testing framework |
pytest | Full-featured testing framework |
mock | Library for mocks and stubs |
coverage | Measure test coverage |
Thorough testing leads to more robust code.
Refactoring techniques make code cleaner and more maintainable. However, avoid refactoring in these cases:
Sometimes pragmatism dictates leaving less ideal code alone over refactoring!
Writing clean Python code takes knowledge, experience and discipline. But the long term benefits make it incredibly worthwhile. This guide provided techniques to start writing cleaner and more Pythonic code immediately.
Clean code is code that is readable, simple, modular and well-tested in addition to being functionally correct. In contrast, working code just focuses on correctness without emphasizing understandability, maintainability and extendibility.
Initially it often does take more time to write clean code due to the additional emphasis on aspects like decomposition, naming, testing etc. However, this pays off in much lower maintenance costs down the line. Automated tools also help speed up writing clean code.
Point out tangible benefits like reduced bugs, easier onboarding of new developers, improved ability to add features quickly. Set a positive example by writing clean code yourself. Introduce internal code reviews.
100% coverage should not be the primary goal. Aim for high coverage of critical paths and edge cases. Low value coverage just for the sake of 100% leads to slower tests and unnecessary maintenance of tests.
A complete rewrite is often infeasible. Instead, incrementally refactor parts of legacy code touched for new features. Over time, the quality can be improved significantly. A full rewrite also discards business knowledge embedded in legacy code.
This comprehensive guide explored various principles, techniques and tools for writing clean Python code. We looked at practical methods to improve code quality like descriptive naming, modularity, limited complexity, reuse, testing, linting, formatting and refactoring. Mastering these clean coding practices requires diligence and experience. However, the long term maintainability and extendibility benefits are invaluable. Companies like Google, Facebook and Microsoft place a premium on clean coding practices due to the amplified productivity and reduced bugs achieved by their developers. With the techniques covered in this guide, any Python developer can start levelling up their skills and writing more readable, maintainable and Pythonic code.
If you enjoyed this article you might like to read Clean Code with Python: A Beginner's Guide to Writing Readable and Maintainable Code or consider one of our training courses below.
At JBI Training, we understand that the success of every software project hinges on the quality of the codebase. That's why we're dedicated to equipping developers with the skills they need to produce clean, efficient, and maintainable code. Our comprehensive training programs cover a range of programming languages and practices, ensuring that you're always ahead of the curve in the ever-evolving world of software development.
Why Train with JBI Training?
At JBI Training, we don't just teach code; we empower developers to excel in their careers and contribute to high-quality software projects. Our instructors are industry experts with real-world experience, and our courses are designed to be practical, hands-on, and up-to-date with the latest industry trends. Join us on a journey to become a more proficient and effective developer, armed with the knowledge and skills you need to make a lasting impact in the world of software development.
CONTACT
+44 (0)20 8446 7555
Copyright © 2024 JBI Training. All Rights Reserved.
JB International Training Ltd - Company Registration Number: 08458005
Registered Address: Wohl Enterprise Hub, 2B Redbourne Avenue, London, N3 2BS
Modern Slavery Statement & Corporate Policies | Terms & Conditions | Contact Us