Python Basics — Scope Best Practices
Scope Best Practices
Writing clean, well-scoped code makes your programs easier to read, test, and debug. Here are the essential practices every Python developer should follow.
1. Minimize Global State
Global variables are shared across your entire module. The more you have, the harder it is to track what’s changing and where.
# ❌ Too much global state
user_name = ""
user_age = 0
user_email = ""
is_logged_in = False
def login(name, age, email):
global user_name, user_age, user_email, is_logged_in
user_name = name
user_age = age
user_email = email
is_logged_in = True
# ✅ Encapsulate in a dictionary or class
def create_user(name, age, email):
return {
"name": name,
"age": age,
"email": email,
"logged_in": True
}
user = create_user("Alice", 30, "alice@example.com")
2. Pass Data Explicitly as Arguments
Functions that receive all their inputs as arguments are easier to understand and test:
# ❌ Depends on global state — hidden dependency
tax_rate = 0.18
def calculate_total(price):
return price + (price * tax_rate) # Where does tax_rate come from?
# ✅ Explicit — everything the function needs is passed in
def calculate_total(price, tax_rate):
return price + (price * tax_rate)
total = calculate_total(1000, 0.18)
print(total) # 1180.0
3. Use Return Values Over Mutations
Pure functions — functions that take input and return output without changing external state — are the easiest to reason about:
# ❌ Mutates external data
results = []
def process_item(item):
results.append(item.upper()) # side effect
for item in ["hello", "world"]:
process_item(item)
# ✅ Returns new data
def process_items(items):
return [item.upper() for item in items]
results = process_items(["hello", "world"])
print(results) # ['HELLO', 'WORLD']
4. Constants — UPPER_CASE Naming Convention
Variables that shouldn’t change are called constants. Python doesn’t enforce immutability, but the naming convention signals intent:
# ✅ Constants in UPPER_CASE
MAX_RETRIES = 3
API_BASE_URL = "https://api.example.com"
DEFAULT_TIMEOUT = 30
PI = 3.14159
# Use them in functions
def make_request(endpoint, timeout=DEFAULT_TIMEOUT):
for attempt in range(MAX_RETRIES):
url = f"{API_BASE_URL}/{endpoint}"
print(f"Attempt {attempt + 1}: {url} (timeout={timeout}s)")
# ... make the request
break
make_request("users")
# Attempt 1: https://api.example.com/users (timeout=30s)
5. Module-Level Scope Organization
Structure your Python files in a clear, predictable order:
"""Module docstring — what this module does."""
# 1. Imports (stdlib → third-party → local)
import os
import sys
from datetime import datetime
# 2. Constants
MAX_SIZE = 1024
DEFAULT_NAME = "unnamed"
# 3. Module-level functions
def helper():
pass
def main_function():
pass
# 4. Classes
class MyClass:
pass
# 5. Entry point
if __name__ == "__main__":
main_function()
6. Keep Functions Small and Focused
Small functions naturally have smaller scopes with fewer variables to track:
# ❌ One big function with many local variables
def process_order(order):
# 50 lines of discount logic
# 30 lines of tax calculation
# 20 lines of shipping logic
# 15 lines of notification logic
pass
# ✅ Split into focused functions
def calculate_discount(order):
"""Calculate discount based on order value."""
if order["total"] > 5000:
return order["total"] * 0.10
return 0
def calculate_tax(subtotal, tax_rate=0.18):
"""Calculate tax on the subtotal."""
return subtotal * tax_rate
def calculate_shipping(weight, distance):
"""Calculate shipping cost."""
return weight * 0.5 + distance * 0.1
def process_order(order):
discount = calculate_discount(order)
subtotal = order["total"] - discount
tax = calculate_tax(subtotal)
shipping = calculate_shipping(order["weight"], order["distance"])
return subtotal + tax + shipping
7. Avoid Shadowing Built-ins
Never use Python built-in names as variable names:
# ❌ Shadows built-ins
list = [1, 2, 3] # Now list() is broken!
dict = {"key": "value"} # Now dict() is broken!
str = "hello" # Now str() is broken!
input = "some text" # Now input() is broken!
# ✅ Use descriptive names instead
numbers = [1, 2, 3]
config = {"key": "value"}
greeting = "hello"
user_input = "some text"
Quick Reference Checklist
| Practice | Do | Don’t |
|---|---|---|
| Global state | Minimize; use constants | Scatter mutable globals everywhere |
| Function inputs | Pass explicitly as arguments | Rely on globals implicitly |
| Function outputs | Return values | Mutate external variables |
| Constants | UPPER_CASE naming |
Magic numbers in code |
| Built-in names | Use descriptive alternatives | Shadow with list, str, dict |
| File organization | Imports → Constants → Functions → Classes → Main | Random order |
| Function size | Small, focused, single purpose | One giant function |
Summary
- Keep global state to a minimum — prefer passing data through arguments
- Write pure functions that return values instead of mutating external state
- Name constants in UPPER_CASE to signal they shouldn’t change
- Organize modules in a consistent order: imports, constants, functions, classes, main
- Never shadow built-in names like
list,dict,str - Keep functions small and focused for naturally cleaner scopes