Python & Django Best Practices
Learn industry best practices including PEP 8, type hints, virtual environments, testing strategies, logging, and production-ready code standards.
๐ PEP 8 & Code Style
- What is PEP 8? PEP 8 is Python's official style guide for writing readable and consistent code.Key Guidelines:
- Use 4 spaces for indentation (not tabs)
- Line length: 79 characters max
- Use snake_case for functions and variables
- Use PascalCase for class names
- Use UPPERCASE for constants
- Two blank lines between top-level functions/classes
- One blank line between methods in a class
- Imports at the top, grouped: standard โ third-party โ local
# Good def calculate_total_price(items): total = 0 for item in items: total += item.price return total class ShoppingCart: MAX_ITEMS = 100 def __init__(self): self.items = [] def add_item(self, item): self.items.append(item) - Tools for Code Quality flake8 - Lintingblack - Formattingpylint - Analysismypy - Type checkingisort - Import sorting
# Install tools pip install flake8 black pylint mypy isort # Run checks flake8 . black --check . pylint myproject/ mypy myproject/ isort --check-only .
๐ค Type Hints
- Using Type Hints in Python Type hints improve code readability, enable static type checking, and provide better IDE support.
from typing import List, Dict, Optional, Union, Tuple # Basic types def greet(name: str) -> str: return f"Hello, {name}" # Collections def process_items(items: List[int]) -> Dict[str, int]: return {"total": sum(items), "count": len(items)} # Optional (can be None) def find_user(user_id: int) -> Optional[User]: try: return User.objects.get(id=user_id) except User.DoesNotExist: return None # Union (multiple types) def parse_value(value: Union[str, int, float]) -> float: return float(value) # Tuple def get_coordinates() -> Tuple[float, float]: return (40.7128, -74.0060) # Class attributes class User: name: str age: int email: Optional[str] = None def __init__(self, name: str, age: int): self.name = name self.age = age
๐ Virtual Environments
- Managing Virtual Environments Virtual environments isolate project dependencies, preventing conflicts between projects.
# Create virtual environment python3 -m venv venv # Activate # Linux/Mac source venv/bin/activate # Windows venv\Scripts\activate # Install dependencies pip install -r requirements.txt # Save dependencies pip freeze > requirements.txt # Deactivate deactivate
- Using Poetry (Modern Alternative)
# Install Poetry curl -sSL https://install.python-poetry.org | python3 - # Initialize project poetry init # Add dependencies poetry add django djangorestframework # Add dev dependencies poetry add --dev pytest black flake8 # Install all dependencies poetry install # Activate shell poetry shell
๐งช Testing
- Testing Best Practices
# test_models.py from django.test import TestCase from .models import Product class ProductTestCase(TestCase): def setUp(self): self.product = Product.objects.create( name="Test Product", price=99.99 ) def test_product_creation(self): self.assertEqual(self.product.name, "Test Product") self.assertEqual(self.product.price, 99.99) def test_product_str(self): self.assertEqual(str(self.product), "Test Product") # test_views.py from rest_framework.test import APITestCase from rest_framework import status class ProductAPITestCase(APITestCase): def test_list_products(self): response = self.client.get('/api/products/') self.assertEqual(response.status_code, status.HTTP_200_OK) def test_create_product(self): data = {'name': 'New Product', 'price': 149.99} response = self.client.post('/api/products/', data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - Using Pytest
# Install pip install pytest pytest-django # pytest.ini [pytest] DJANGO_SETTINGS_MODULE = myproject.settings python_files = tests.py test_*.py *_tests.py # test_example.py import pytest from .models import Product @pytest.mark.django_db def test_product_creation(): product = Product.objects.create(name="Test", price=99.99) assert product.name == "Test" assert product.price == 99.99 # Run tests pytest pytest -v # Verbose pytest --cov # With coverage
๐ Logging
- Proper Logging Configuration
# settings.py LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '{levelname} {asctime} {module} {message}', 'style': '{', }, }, 'handlers': { 'file': { 'level': 'INFO', 'class': 'logging.FileHandler', 'filename': 'app.log', 'formatter': 'verbose', }, 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'verbose', }, }, 'root': { 'handlers': ['console', 'file'], 'level': 'INFO', }, 'loggers': { 'django': { 'handlers': ['console', 'file'], 'level': 'INFO', 'propagate': False, }, }, }# Using logger import logging logger = logging.getLogger(__name__) def process_order(order_id): logger.info(f"Processing order {order_id}") try: # Process order logger.debug(f"Order {order_id} details fetched") except Exception as e: logger.error(f"Error processing order {order_id}: {e}") raise
๐ฆ Package Management
- Requirements Management
# requirements.txt - Production Django==4.2.0 djangorestframework==3.14.0 psycopg2-binary==2.9.6 gunicorn==20.1.0 redis==4.5.5 # requirements-dev.txt - Development -r requirements.txt pytest==7.3.1 pytest-django==4.5.2 black==23.3.0 flake8==6.0.0 mypy==1.3.0 # Install pip install -r requirements.txt pip install -r requirements-dev.txt
๐ Security Best Practices
- Security Checklist
- โ Never commit secrets (use .env files)
- โ Use environment variables for sensitive data
- โ Set DEBUG=False in production
- โ Use HTTPS only (SECURE_SSL_REDIRECT=True)
- โ Set secure cookie flags
- โ Implement CSRF protection
- โ Validate all user inputs
- โ Use parameterized queries (ORM does this)
- โ Keep dependencies updated
- โ Use strong password hashing (Django default is good)
- โ Implement rate limiting
- โ Set proper CORS policies
# .env file SECRET_KEY=your-secret-key-here DEBUG=False DATABASE_URL=postgresql://user:pass@localhost/dbname ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com # settings.py import environ env = environ.Env() environ.Env.read_env() SECRET_KEY = env('SECRET_KEY') DEBUG = env.bool('DEBUG', default=False) ALLOWED_HOSTS = env.list('ALLOWED_HOSTS') # Security settings SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True X_FRAME_OPTIONS = 'DENY'
๐ Performance Tips
- Optimization Strategies
- Use database indexes on frequently queried fields
- Use select_related() and prefetch_related()
- Implement caching (Redis/Memcached)
- Use database connection pooling
- Optimize queries (avoid N+1 problems)
- Use pagination for large datasets
- Compress static files
- Use CDN for static files
- Enable query optimization in Django Debug Toolbar
- Use async views for I/O-bound operations
- Profile code with cProfile or Django Silk
๐ Documentation
- Code Documentation Best Practices
def calculate_discount(price: float, discount_percent: float) -> float: """ Calculate the discounted price. Args: price (float): Original price of the item discount_percent (float): Discount percentage (0-100) Returns: float: Final price after discount Raises: ValueError: If discount_percent is not between 0 and 100 Example: >>> calculate_discount(100, 20) 80.0 """ if not 0 <= discount_percent <= 100: raise ValueError("Discount must be between 0 and 100") return price * (1 - discount_percent / 100)