Python Basics — Python Best Practices
Python Best Practices
Writing code that works is the first step. Writing code that’s readable, maintainable, and Pythonic is the mark of a professional.
PEP 8 Summary
PEP 8 is Python’s official style guide. Here are the most important rules:
Naming Conventions
# Variables and functions: snake_case
user_name = "Alice"
def calculate_total():
pass
# Classes: PascalCase
class ShoppingCart:
pass
# Constants: UPPER_SNAKE_CASE
MAX_RETRIES = 3
DATABASE_URL = "localhost"
# Private: leading underscore
_internal_cache = {}
def _helper_function():
pass
# "Really private": double underscore (name mangling)
class MyClass:
__secret = 42
Indentation and Spacing
# ✅ 4 spaces per indentation level (never tabs)
if condition:
do_something()
# ✅ Spaces around operators
x = 5 + 3
result = x * 2
# ✅ Space after comma, not before
items = [1, 2, 3]
func(arg1, arg2)
# ✅ No space inside brackets
items[0] # not items[ 0 ]
func(arg) # not func( arg )
# ✅ Blank lines
# 2 blank lines before top-level functions/classes
# 1 blank line between methods in a class
Import Order (PEP 8)
# 1. Standard library
import os
import sys
from datetime import datetime
# 2. Third-party packages
import requests
from flask import Flask
# 3. Local/project imports
from myapp.models import User
from myapp.utils import validate
Line Length
# ✅ Max 79 characters for code, 72 for docstrings
# ✅ Break long lines with parentheses
result = (
first_value
+ second_value
- third_value
)
# ✅ Or with backslash (less preferred)
total = first_value + \
second_value + \
third_value
Pythonic Code Patterns
Use Truthiness
# ❌ Verbose
if len(items) > 0:
process(items)
if name != "":
greet(name)
if result is not None:
use(result)
# ✅ Pythonic
if items:
process(items)
if name:
greet(name)
if result is not None: # Keep this one — None check should be explicit
use(result)
Use Unpacking
# ❌ Index access
point = (3, 4)
x = point[0]
y = point[1]
# ✅ Unpacking
x, y = (3, 4)
# ✅ Swap variables
a, b = b, a
# ✅ Ignore values with _
name, _, age = ("Alice", "unused", 30)
first, *rest = [1, 2, 3, 4, 5] # first=1, rest=[2,3,4,5]
Use enumerate() and zip()
# ❌ Manual index
for i in range(len(items)):
print(i, items[i])
# ✅ enumerate
for i, item in enumerate(items):
print(i, item)
# ❌ Parallel iteration with index
for i in range(len(names)):
print(names[i], scores[i])
# ✅ zip
for name, score in zip(names, scores):
print(name, score)
Use Comprehensions
# ❌ Manual loop
squares = []
for x in range(10):
squares.append(x ** 2)
# ✅ List comprehension
squares = [x ** 2 for x in range(10)]
# ✅ Dict comprehension
word_lengths = {w: len(w) for w in words}
# ✅ Set comprehension
unique_lengths = {len(w) for w in words}
Use f-Strings
name = "Alice"
age = 30
# ❌ Old style
print("Hello, %s! You are %d." % (name, age))
print("Hello, {}! You are {}.".format(name, age))
# ✅ f-strings (Python 3.6+)
print(f"Hello, {name}! You are {age}.")
print(f"Debug: {name=}, {age=}") # Python 3.8+
EAFP vs LBYL
Two approaches to handling potential problems:
LBYL — “Look Before You Leap”
# Check first, then act
if "key" in dictionary:
value = dictionary["key"]
else:
value = default
if os.path.exists(filename):
with open(filename) as f:
data = f.read()
EAFP — “Easier to Ask Forgiveness than Permission”
# Try it, handle failure
try:
value = dictionary["key"]
except KeyError:
value = default
try:
with open(filename) as f:
data = f.read()
except FileNotFoundError:
data = ""
Python favors EAFP — it’s often faster (one operation instead of two) and avoids race conditions (file could be deleted between check and open).
But for simple checks, LBYL with .get() is cleaner:
# ✅ Best of both worlds
value = dictionary.get("key", default)
Writing Readable Code
Meaningful Names
# ❌ Cryptic
def p(l, n):
return [x for x in l if x > n]
# ✅ Clear
def filter_above_threshold(values, threshold):
return [v for v in values if v > threshold]
Small, Focused Functions
# ❌ One giant function doing everything
def process_user_data(filepath):
# ... 200 lines of code ...
pass
# ✅ Small, composable functions
def load_user_data(filepath):
"""Load user data from CSV file."""
...
def validate_users(users):
"""Validate user records, return valid ones."""
...
def generate_report(users):
"""Generate summary report from user data."""
...
# Clear pipeline
data = load_user_data("users.csv")
valid_users = validate_users(data)
report = generate_report(valid_users)
Use Docstrings
def calculate_bmi(weight_kg, height_m):
"""
Calculate Body Mass Index (BMI).
Args:
weight_kg: Weight in kilograms.
height_m: Height in meters.
Returns:
BMI as a float, rounded to 1 decimal place.
Raises:
ValueError: If weight or height is non-positive.
"""
if weight_kg <= 0 or height_m <= 0:
raise ValueError("Weight and height must be positive")
return round(weight_kg / (height_m ** 2), 1)
Code Organization Tips
Project Structure
my_project/
├── README.md
├── requirements.txt
├── main.py
├── config.py
├── utils/
│ ├── __init__.py
│ ├── helpers.py
│ └── validators.py
├── models/
│ ├── __init__.py
│ └── user.py
└── tests/
├── __init__.py
├── test_helpers.py
└── test_user.py
Separation of Concerns
# ❌ Mixed concerns
def save_user(name, age):
if not name:
print("Error: name required") # UI concern
return
conn = sqlite3.connect("app.db") # DB concern
conn.execute("INSERT ...", (name, age))
conn.commit()
print(f"Saved {name}") # UI concern
# ✅ Separated
def validate_user(name, age):
"""Pure validation logic."""
if not name:
raise ValueError("Name is required")
if age < 0:
raise ValueError("Age must be non-negative")
def save_to_db(name, age, db_path="app.db"):
"""Database persistence."""
conn = sqlite3.connect(db_path)
conn.execute("INSERT ...", (name, age))
conn.commit()
Version Control Basics
Start every project with git:
# Initialize a new repository
git init
# Add files
git add .
# Commit
git commit -m "Initial commit"
# Common workflow
git add modified_file.py
git commit -m "Add user validation function"
# Check status
git status
git log --oneline
Write Good Commit Messages
✅ "Add email validation to user registration"
✅ "Fix off-by-one error in pagination"
✅ "Refactor database connection to use context manager"
❌ "Fixed stuff"
❌ "Update"
❌ "asdfgh"
Summary
| Practice | Why |
|---|---|
| Follow PEP 8 | Consistent, readable code |
| Use f-strings | Clean string formatting |
| Prefer EAFP | Pythonic error handling |
| Write docstrings | Document intent and usage |
| Small functions | Easier to test and reuse |
| Use comprehensions | Concise, expressive code |
Use with for files |
Automatic resource cleanup |
Use git |
Track changes, collaborate |
Next up: Next Steps →