Welcome to the world of Python! Whether you're transitioning from Perl, Java, or another language, Python offers a clean and readable approach to software development. This guide covers essential best practices that will help you write better Python code from day one.
PEP 8 is the official style guide for Python code. It covers everything from naming conventions to code layout. Key points:
Always use virtual environments for your projects. This keeps dependencies isolated:
# Using venv (built-in since Python 3.3)
python -m venv myproject_env
source myproject_env/bin/activate # On Windows: .\myproject_env\Scripts\activate
# Or use poetry for modern dependency management
poetry new myproject
cd myproject
poetry add requests httpx
Python 3.5+ supports type hints. They improve code clarity and enable static analysis:
from typing import List, Optional
def process_users(user_ids: List[int], include_inactive: bool = False) -> List[dict]:
"""Process user records and return formatted data."""
results: List[dict] = []
for uid in user_ids:
user = fetch_user(uid)
if user or include_inactive:
results.append(format_user(user))
return results
Introduced in Python 3.6, f-strings are the most readable way to format strings:
name = "Python"
version = 3.12
# Old way (still works but less readable)
print("Welcome to %s %.1f!" % (name, version))
print("Welcome to {0} {1:.1f}!".format(name, version))
# Modern way with f-strings
print(f"Welcome to {name} {version:.1f}!") # Clean and readable!
# Even supports expressions
print(f"Next version: {version + 1:.1f}")
Handle exceptions specifically and never use bare except clauses:
# Good: Catch specific exceptions
from requests import RequestException, Timeout
try:
response = requests.get(url, timeout=30)
data = response.json()
except Timeout:
logger.warning(f"Request to {url} timed out")
raise ServiceUnavailable("External service timeout")
except RequestException as e:
logger.error(f"Request failed: {e}")
raise ServiceUnavailable(f"External service error: {e}")
except ValueError as e:
# JSON parsing failed
logger.error(f"Invalid JSON response: {e}")
raise DataError("Invalid response format")
# Bad: Never do this
try:
do_something()
except: # Catches everything including KeyboardInterrupt!
pass
Pythonic code often uses comprehensions for cleaner iteration:
# List comprehension - clean and efficient
squares = [x**2 for x in range(10) if x % 2 == 0]
# Dictionary comprehension
users_by_id = {user.id: user for user in user_list}
# Generator expression for memory efficiency (lazy evaluation)
total = sum(x**2 for x in range(1000000)) # No list created in memory!
Always use context managers (the with statement) for resources:
# File handling - automatically closes file
with open('data.txt', 'r') as f:
content = f.read()
# Database connections
with sqlite3.connect('mydb.db') as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
# Connection automatically committed/closed
# Custom context managers with contextlib
from contextlib import contextmanager
@contextmanager
def managed_resource():
resource = acquire_resource()
try:
yield resource
finally:
resource.release()
Follow PEP 257 for docstrings. Use triple double-quotes and document parameters:
def calculate_discount(price: float, customer_type: str, years_active: int) -> float:
"""Calculate discount percentage based on customer loyalty.
Args:
price: Original item price in GBP
customer_type: 'regular', 'vip', or 'enterprise'
years_active: Number of years as a customer
Returns:
Discounted price after applying appropriate percentage
Raises:
ValueError: If customer_type is not recognized
"""
base_discount = {'regular': 0.05, 'vip': 0.15, 'enterprise': 0.25}
if customer_type not in base_discount:
raise ValueError(f"Unknown customer type: {customer_type}")
loyalty_bonus = min(years_active * 0.01, 0.10) # Max 10% loyalty bonus
discount_rate = base_discount[customer_type] + loyalty_bonus
return price * (1 - discount_rate)
Modern Python testing is dominated by pytest. It's powerful and simple:
# test_calculator.py
import pytest
from calculator import add, divide
def test_add_positive_numbers():
assert add(2, 3) == 5
def test_add_negative_numbers():
assert add(-1, -1) == -2
def test_divide_by_zero_raises():
with pytest.raises(ZeroDivisionError):
divide(10, 0)
# Parametrized tests for multiple cases
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(1, 1, 2),
(0, 0, 0),
(-1, 1, 0),
])
def test_add_various_cases(a, b, expected):
assert add(a, b) == expected
Set up these tools for a professional Python workflow:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/psf/black
rev: 23.12.1
hooks:
- id: black
language_version: python3.12
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/pycqa/flake8
rev: 6.1.0
hooks:
- id: flake8
Python's philosophy emphasizes readability and simplicity. By following these best practices, you'll write cleaner, more maintainable code that other developers (and your future self) will appreciate!
Key takeaways:
Happy coding! ๐๐