Table of Contents
- Follow PEP 8 Style Guidelines
- Use Meaningful Names
- Avoid Global Variables
- Leverage Built-in Functions and Libraries
- Prefer List Comprehensions Over Loops
- Add Type Hints for Clarity
- Handle Errors Gracefully
- Write Descriptive Docstrings and Comments
- Avoid Code Duplication (DRY Principle)
- Choose Efficient Data Structures
- Profile Before Optimizing
- Write Tests for Reliability
- Adopt Modern Python Features
- Conclusion
- References
1. Follow PEP 8 Style Guidelines
PEP 8 is Python’s official style guide, designed to promote consistency across codebases. Adhering to PEP 8 makes your code readable for other Python developers (and future you!).
Key PEP 8 Rules:
- Indentation: Use 4 spaces (not tabs).
- Line Length: Limit lines to 79 characters (88 for code with tools like
black). - Naming Conventions:
- Variables/functions:
snake_case(e.g.,user_age,calculate_total). - Classes:
PascalCase(e.g.,UserAccount,DataProcessor). - Constants:
UPPER_SNAKE_CASE(e.g.,MAX_RETRY_ATTEMPTS).
- Variables/functions:
- Whitespace:
- Add a space after commas:
(a, b, c)instead of(a,b,c). - Avoid spaces around function arguments:
def func(x):notdef func( x ):.
- Add a space after commas:
Example:
# Bad
def CalculateTotal(Items):
total=0
for i in Items: total +=i
return total
# Good
def calculate_total(items):
total = 0
for item in items:
total += item
return total
Tool Tip: Use linters like pylint or formatters like black to auto-enforce PEP 8.
2. Use Meaningful Names
Variable, function, and class names should clearly describe their purpose. Avoid vague names like x, data, or process—they force readers to guess intent.
Guidelines:
- Be specific:
user_ageis better thanage(unless context makesageunambiguous). - Avoid abbreviations (unless universally understood, e.g.,
idfor identifier). - Reflect type: For booleans, use prefixes like
is_,has_, orshould_(e.g.,is_valid,has_permission).
Example:
# Bad
a = 25 # What is 'a'? Age? Score?
def p(d): # What does 'p' do? Print? Process?
return sum(d) / len(d)
# Good
user_age = 25
def calculate_average(numbers):
return sum(numbers) / len(numbers)
3. Avoid Global Variables
Global variables introduce hidden dependencies and make code harder to debug (since any part of the code can modify them).
Why They’re Harmful:
- Side effects: Changing a global variable in one function affects all uses elsewhere.
- Testing complexity: Functions relying on globals are harder to test in isolation.
Alternatives:
- Pass variables as function parameters.
- Use classes to encapsulate state (via instance variables).
Example:
# Bad: Global variable
total = 0
def add_to_total(value):
global total # Modifies global state
total += value
# Good: Use parameters and return values
def add_to_total(current_total, value):
return current_total + value
# Usage:
current_total = 0
current_total = add_to_total(current_total, 5) # Explicit and testable
4. Leverage Built-in Functions and Libraries
Python’s standard library is rich with optimized, battle-tested functions. Reusing them improves readability and performance (they’re often implemented in C!).
Must-Know Built-ins:
sum(): Sum iterables (faster than manual loops).len(): Get length of objects (optimized for all standard types).map()/filter(): Apply functions to iterables (but prefer list comprehensions for readability).enumerate(): Loop with indices (avoidsrange(len(...))).zip(): Pair elements from multiple iterables.
Example:
# Bad: Manual sum with loop
numbers = [1, 2, 3, 4]
total = 0
for num in numbers:
total += num
# Good: Use built-in sum()
total = sum(numbers)
# Bad: Loop with index
fruits = ["apple", "banana", "cherry"]
for i in range(len(fruits)):
print(f"{i}: {fruits[i]}")
# Good: Use enumerate()
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
5. Prefer List Comprehensions Over Loops
List comprehensions are concise, readable, and often faster than for loops for creating lists. They replace boilerplate code with a single line.
When to Use:
- Simple transformations or filters (e.g.,
[x*2 for x in numbers if x > 0]).
When to Avoid:
- Complex logic (nested comprehensions can become unreadable).
Example:
# Bad: Loop to create a list of squares
numbers = [1, 2, 3, 4]
squares = []
for num in numbers:
squares.append(num **2)
# Good: List comprehension
squares = [num** 2 for num in numbers]
# With filtering
even_squares = [num **2 for num in numbers if num % 2 == 0]
Note: Use generator expressions ((x*2 for x in iterable)) for large datasets to save memory (they yield items lazily).
6. Add Type Hints for Clarity
Python is dynamically typed, but type hints (introduced in Python 3.5) make code self-documenting and enable IDEs to catch errors early. They don’t affect runtime but improve collaboration.
How to Use:
- Annotate function parameters and return types with
->. - Use
typingmodule for complex types (e.g.,List[int],Dict[str, float]).
Example:
from typing import List, Optional
def greet(name: str) -> str:
return f"Hello, {name}!"
def calculate_average(numbers: List[float]) -> Optional[float]:
"""Return average of numbers, or None if empty."""
if not numbers:
return None
return sum(numbers) / len(numbers)
Tool Tip: Use mypy to statically check type hints for errors.
7. Handle Errors Gracefully
Use try/except blocks to handle expected errors instead of letting the program crash. Avoid bare except clauses—they hide bugs!
Best Practices:
- Catch specific exceptions (e.g.,
FileNotFoundErrorinstead ofException). - Avoid silent failures: Log errors or inform users.
- Use
elsefor code that runs if no exception occurs, andfinallyfor cleanup (e.g., closing files).
Example:
# Bad: Bare except hides errors
try:
with open("data.txt") as f:
content = f.read()
except: # Catches EVERYTHING (including KeyboardInterrupt!)
print("Something went wrong")
# Good: Specific exception + cleanup
try:
with open("data.txt") as f:
content = f.read()
except FileNotFoundError:
print("Error: File 'data.txt' not found.")
else:
print("File read successfully!")
finally:
print("This runs whether or not an error occurred.")
8. Write Descriptive Docstrings and Comments
Docstrings (multi-line comments) explain what and why code does something. Comments should clarify “why” (not “what”—the code should show that).
Docstring Formats:
- Google Style: Human-readable, widely used.
- reStructuredText: Used with tools like
Sphinxfor documentation generation.
Example (Google Style):
def calculate_average(numbers: List[float]) -> Optional[float]:
"""Compute the average of a list of numbers.
Args:
numbers: List of numerical values (int or float).
Returns:
float: Average of the numbers.
None: If `numbers` is empty.
Raises:
TypeError: If `numbers` contains non-numerical values.
"""
if not numbers:
return None
return sum(numbers) / len(numbers)
Tip: Comments should explain “why” a decision was made (e.g., # Use UTC to avoid timezone conflicts).
9. Avoid Code Duplication (DRY Principle)
The “Don’t Repeat Yourself” (DRY) principle states that every piece of logic should have a single source of truth. Duplicated code leads to inconsistencies when requirements change.
How to Fix Duplication:
- Extract repeated code into functions or classes.
- Use inheritance or composition for shared behavior in classes.
Example:
# Bad: Duplicated logic
def print_user_details(user: dict) -> None:
print(f"Name: {user['name']}")
print(f"Age: {user['age']}")
print(f"Email: {user['email']}") # Repeated in print_admin_details
def print_admin_details(admin: dict) -> None:
print(f"Name: {admin['name']}")
print(f"Age: {admin['age']}")
print(f"Email: {admin['email']}")
print(f"Role: Admin")
# Good: Extract shared logic into a function
def print_basic_details(person: dict) -> None:
print(f"Name: {person['name']}")
print(f"Age: {person['age']}")
print(f"Email: {person['email']}")
def print_user_details(user: dict) -> None:
print_basic_details(user)
def print_admin_details(admin: dict) -> None:
print_basic_details(admin)
print(f"Role: Admin")
10. Choose Efficient Data Structures
Python’s data structures have different performance characteristics. Choosing the right one can drastically improve efficiency.
Common Structures and Use Cases:
| Structure | Use Case | Lookup Speed |
|---|---|---|
list | Ordered, mutable collection | O(n) |
tuple | Immutable ordered collection | O(n) |
set | Unique elements, membership checks | O(1) |
dict (3.7+) | Key-value pairs, ordered | O(1) |
Example: Fast Membership Checks
# Bad: Slow lookup with list (O(n))
fruits = ["apple", "banana", "cherry"]
if "banana" in fruits: # Checks every element
print("Found!")
# Good: Fast lookup with set (O(1))
fruits_set = {"apple", "banana", "cherry"}
if "banana" in fruits_set: # Instant check
print("Found!")
11. Profile Before Optimizing
“Premature optimization is the root of all evil.” — Donald Knuth. Optimize only after identifying bottlenecks with profiling tools.
How to Profile:
- Use
cProfileto measure function runtime. - Focus on code that runs frequently (e.g., loops in a web server).
Example:
# Profile a script
python -m cProfile -s cumulative my_script.py
Output Snippet:
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 2.500 2.500 my_script.py:1(main)
100 0.010 0.000 2.490 0.025 my_script.py:5(slow_function)
Here, slow_function is called 100 times and takes 2.49s—optimize this first!
12. Write Tests for Reliability
Tests ensure code works as expected, even after changes. Use unittest (built-in) or pytest (more concise) for unit tests.
Best Practices:
- Test edge cases (e.g., empty inputs, large numbers).
- Keep tests independent and fast.
Example with pytest:
# test_math.py
def test_addition():
assert 2 + 2 == 4
def test_average():
from my_module import calculate_average
assert calculate_average([1, 2, 3]) == 2.0
assert calculate_average([]) is None # Edge case
Run with: pytest test_math.py -v
13. Adopt Modern Python Features
Python evolves rapidly—use modern features to write cleaner code:
- f-strings (Python 3.6+): Concise string formatting (
f"Hello {name}"). - Walrus operator (Python 3.8+): Assign and use variables in expressions (
if (n := len(data)) > 10:). - Pathlib (Python 3.4+): Object-oriented file paths (
Path("data") / "file.txt"). - Context managers (
with): Safely handle resources (files, network connections).
Example: f-strings vs. Old Formatting
name = "Alice"
age = 30
# Bad: % formatting (error-prone)
print("Hello %s, you are %d." % (name, age))
# Good: f-string (readable and fast)
print(f"Hello {name}, you are {age}.")
Conclusion
Writing clean and efficient Python code is a skill that improves with practice. By following these best practices—from PEP 8 compliance to strategic optimization—you’ll create code that’s readable, maintainable, and performant. Remember: prioritize readability first, then optimize based on data, and always test rigorously.
Consistency is key—adopt these habits, and your future self (and teammates) will thank you!