PEP 8 y estilo de codigo
PEP 8 es la guia de estilo oficial de Python. Seguirla asegura que tu codigo sea consistente y legible. El desarrollo moderno de Python usa herramientas automatizadas como Ruff (linting y formato), Black (formato) e isort (ordenamiento de imports) para aplicar el estilo automaticamente.
# PEP 8 Naming Conventions
my_variable = "snake_case for variables"
MY_CONSTANT = "UPPER_CASE for constants"
def my_function():
"""snake_case for functions."""
pass
class MyClass:
"""PascalCase for classes."""
def my_method(self):
"""snake_case for methods."""
pass
_private_var = "leading underscore for private"
__name_mangled = "double underscore for name mangling"
# Good formatting
def calculate_total(
items: list[dict],
tax_rate: float = 0.08,
discount: float = 0.0,
) -> float:
"""
Calculate the total price including tax and discount.
Args:
items: List of items with 'price' and 'quantity' keys.
tax_rate: Tax rate as a decimal (default 8%).
discount: Discount as a decimal (default 0%).
Returns:
The total price after tax and discount.
"""
subtotal = sum(item["price"] * item["quantity"] for item in items)
tax = subtotal * tax_rate
total = (subtotal + tax) * (1 - discount)
return round(total, 2)
Estructura de proyecto
# Recommended project layout
# myproject/
# pyproject.toml # Project config, dependencies, tool settings
# README.md
# LICENSE
# .gitignore
# .env # Environment variables (NOT in git!)
# .env.example # Template for .env
# src/
# myproject/
# __init__.py
# main.py # Entry point
# config.py # Configuration management
# models/
# __init__.py
# user.py
# product.py
# services/
# __init__.py
# user_service.py
# email_service.py
# api/
# __init__.py
# routes/
# __init__.py
# users.py
# products.py
# middleware.py
# dependencies.py
# utils/
# __init__.py
# helpers.py
# validators.py
# tests/
# __init__.py
# conftest.py # Shared fixtures
# test_user_service.py
# test_api_users.py
# scripts/
# seed_data.py
# migrate.py
Gestion de configuracion
# config.py - using pydantic-settings
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
app_name: str = "My Application"
debug: bool = False
database_url: str = "sqlite:///./app.db"
secret_key: str
api_key: str
redis_url: str = "redis://localhost:6379"
# Nested settings
cors_origins: list[str] = ["http://localhost:3000"]
max_upload_size: int = 10_000_000 # 10MB
model_config = {
"env_file": ".env",
"env_file_encoding": "utf-8",
}
@lru_cache
def get_settings() -> Settings:
return Settings()
# Usage
settings = get_settings()
print(settings.database_url)
print(settings.debug)
# .env file
# DATABASE_URL=postgresql://user:pass@localhost/mydb
# SECRET_KEY=your-secret-key-here
# API_KEY=your-api-key
# DEBUG=true
Linting y formato con Ruff
# pip install ruff
# Lint your code
# ruff check .
# Auto-fix issues
# ruff check --fix .
# Format code (like Black)
# ruff format .
# pyproject.toml configuration
# [tool.ruff]
# target-version = "py312"
# line-length = 100
#
# [tool.ruff.lint]
# select = [
# "E", # pycodestyle errors
# "W", # pycodestyle warnings
# "F", # pyflakes
# "I", # isort
# "N", # pep8-naming
# "UP", # pyupgrade
# "B", # flake8-bugbear
# "SIM", # flake8-simplify
# "S", # flake8-bandit (security)
# ]
# ignore = ["E501"] # Ignore line length (handled by formatter)
#
# [tool.ruff.lint.per-file-ignores]
# "tests/**/*.py" = ["S101"] # Allow assert in tests
Logging
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# Create a logger
logger = logging.getLogger(__name__)
# Use appropriate levels
logger.debug("Detailed debugging info")
logger.info("General information")
logger.warning("Something unexpected happened")
logger.error("An error occurred")
logger.critical("System is in a critical state")
# Structured logging with extra data
logger.info("User created", extra={"user_id": 42, "email": "alice@example.com"})
# Exception logging
try:
result = 10 / 0
except ZeroDivisionError:
logger.exception("Failed to calculate result")
# This logs the full traceback automatically
# Production logging config
import logging.config
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
},
"json": {
"class": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(asctime)s %(levelname)s %(name)s %(message)s",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "default",
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "app.log",
"maxBytes": 10_000_000,
"backupCount": 5,
"formatter": "default",
},
},
"root": {
"level": "INFO",
"handlers": ["console", "file"],
},
}
logging.config.dictConfig(LOGGING_CONFIG)
Antipatrones de Python a evitar
# BAD: Mutable default argument
def bad(items=[]):
items.append(1)
return items
# GOOD:
def good(items=None):
if items is None:
items = []
items.append(1)
return items
# BAD: Bare except
try:
risky()
except: # Catches SystemExit, KeyboardInterrupt too!
pass
# GOOD: Specific exception
try:
risky()
except ValueError as e:
logger.error(f"Validation error: {e}")
# BAD: Checking type with ==
if type(x) == int:
pass
# GOOD: isinstance
if isinstance(x, int):
pass
# BAD: Manual resource management
f = open("file.txt")
data = f.read()
f.close() # Might not run if exception occurs!
# GOOD: Context manager
with open("file.txt") as f:
data = f.read()
# BAD: Using global variables
counter = 0
def increment():
global counter
counter += 1
# GOOD: Use a class or pass as argument
class Counter:
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
# BAD: String concatenation in a loop
result = ""
for word in words:
result += word + " "
# GOOD: Use join
result = " ".join(words)
Puntos clave
- Usa Ruff: Linter y formateador moderno y rapido que reemplaza multiples herramientas
- Sigue PEP 8: Un estilo consistente hace el codigo legible y mantenible
- Logging adecuado: Usa el modulo logging, no print(), en produccion
- Variables de entorno: Usa pydantic-settings para configuracion con tipado seguro
- Evita antipatrones: Aprende las trampas comunes y sus soluciones pythonicas