Functions
Functions form the smallest executable unit in Python and are used throughout Django for routing, business logic, utilities, filters, signals, and background operations.
Where functions appear in Django
- HTTP request handlers (function-based views)
- Utility helpers in
utils/ - Template filters and simple template tags
- Signal receivers
- Form validation helpers
- Management command helpers
- Async tasks for Celery or built-in async views
Why functions matter in Django
Functions keep code small, focused, reusable, and testable. They allow separation of concerns, enabling clean views and consistent business logic across the project.
Basic function syntax
def add(a, b):
"""Add two numbers and return the result."""
return a + b
Function parameters
Python supports positional, keyword, default, variable-length, and keyword-only arguments. Django utilities often rely on predictable keyword arguments to reduce ambiguity.
def process_order(order_id, *, include_tax=True, include_shipping=False):
return {
"id": order_id,
"tax": include_tax,
"shipping": include_shipping,
}
Function parameters
Python functions support multiple parameter styles. Each style controls how callers pass values and how functions enforce structure.
1. Positional arguments
def area(length, width):
return length * width
area(5, 3)
2. Keyword arguments
def greet(name, message):
return f"{message}, {name}"
greet(name="Nischal", message="Hello")
3. Default arguments
def power(base, exponent=2):
return base ** exponent
power(5) # uses default exponent=2
power(5, 3)
4. Variable-length positional arguments *args
def total(*numbers):
return sum(numbers)
total(1, 2, 3, 4)
5. Variable-length keyword arguments **kwargs
def build_profile(**info):
return info
build_profile(name="Asha", age=22, city="Pokhara")
6. Keyword-only arguments
Everything after * must be passed by keyword.
def create_user(username, *, is_admin=False, active=True):
return {
"username": username,
"is_admin": is_admin,
"active": active,
}
create_user("ram") # ok
create_user("ram", is_admin=True) # ok
create_user("ram", True, False) # error
Return values
Functions may return any Python object. Django views must return HttpResponse or a subclass, but helpers can return dicts, strings, or complex objects.
Example: function-based view
# views.py
from django.http import HttpResponse
from .utils import render_welcome_message
def welcome_view(request):
message = render_welcome_message(request.user)
return HttpResponse(message)
# utils.py
def render_welcome_message(user):
if user.is_authenticated:
return f"Welcome back, {user.username}!"
return "Welcome, guest!"
Example: utility function for tests
# utils/strings.py
def slugify_title(title: str) -> str:
"""Return a URL-friendly slug for a blog title."""
return "-".join(title.lower().split())
Higher-order functions
Functions can be passed as arguments or returned from other functions. Django middleware and decorators internally rely on higher-order functions.
def apply_twice(func, value):
return func(func(value))
def double(x):
return x * 2
apply_twice(double, 5) # returns 20
Decorators
import time
from functools import wraps
def timing(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"{func.__name__} took {elapsed:.3f}s")
return result
return wrapper
@timing
def expensive_calculation(n):
return n * n
Closures
A closure stores variables from its enclosing scope. Some advanced Django utilities (e.g., dynamic validator factories) use closures.
def min_length_validator(min_len):
def validator(value):
if len(value) < min_len:
raise ValueError(f"Value must be at least {min_len} characters.")
return validator
validate_5 = min_length_validator(5)
Async functions in Django
Django supports async views. Async functions allow concurrency for IO-bound tasks.
# async view
from django.http import JsonResponse
import httpx
async def fetch_data(request):
async with httpx.AsyncClient() as client:
r = await client.get("https://api.example.com/data")
return JsonResponse({"data": r.json()})
Type hints
Type hints help with readability, editor support, and static analysis (e.g., mypy).
from typing import List
def sum_list(nums: List[int]) -> int:
return sum(nums)
Common pitfalls
- Too much logic in views: move heavy logic into services/utilities.
- Mutable default args: avoid lists/dicts as default parameters.
- Circular imports: structure helpers in dedicated modules.
- Ignoring return types: keep function outputs predictable.
- Mixed responsibilities: split large functions into smaller units.
Exercise
Create format_currency(amount, currency="NPR") to produce NPR 1,234.00. Write a unit test asserting correct formatting for several values.
Next step
Move to Modules to organize related functions into reusable packages.