Functions are fundamental to writing reusable, maintainable Python code. Beyond basic parameters and return values, Python supports closures, higher-order functions, type hints, and generators that help developers build flexible and efficient applications.
A function encapsulates reusable logic using the def keyword.
def calculate_total(price: float, quantity: int) -> float:return price * quantityprint(calculate_total(100, 3)) # 300
Python supports default parameters and keyword arguments:
def create_user(name: str, role: str = "user") -> dict:return {"name": name, "role": role}create_user("Alice")create_user("Bob", role="admin")
Key points:
return statement return None.*args and **kwargsUse *args and **kwargs when a function needs to accept a variable number of arguments.
def create_user(*args, **kwargs):print(args) # Positional arguments as a tupleprint(kwargs) # Keyword arguments as a dictionarycreate_user("Alice", 25, role="admin")
You can also unpack existing collections when calling a function:
def add(a: int, b: int) -> int:return a + bprint(add(*(3, 5))) # 8print(add(**{"a": 3, "b": 5})) # 8
These features are useful for wrappers, configurable functions, and decorators.
Python resolves variable names using four scopes:
len() and print().message = "global"def outer():message = "enclosing"def inner():message = "local"print(message)inner()outer() # local
Use global to reassign a module-level variable and nonlocal to reassign a variable in an enclosing function.
Avoid unnecessary global state because it makes applications harder to test and maintain.
A closure is an inner function that retains access to variables from its enclosing scope, even after the outer function has returned.
def make_multiplier(factor: int):def multiply(number: int) -> int:return number * factorreturn multiplydouble = make_multiplier(2)print(double(5)) # 10
Closures are useful for callbacks, configurable functions, and maintaining small amounts of private state.
A lambda is a small anonymous function containing a single expression.
square = lambda x: x ** 2print(square(5)) # 25
A higher-order function accepts another function as an argument or returns a function.
def apply_operation(operation, value):return operation(value)print(apply_operation(square, 4)) # 16
These concepts are commonly used in sorting, callbacks, decorators, and functional programming.
Prefer a regular def function when the logic becomes complex or needs a descriptive name.
Type hints document expected parameter and return types, improving readability and static analysis.
def find_user(user_id: int) -> str | None:if user_id == 1:return "Alice"return None
Python does not normally enforce type annotations at runtime. Tools such as mypy and Pyright can identify type inconsistencies before execution.
Type hints are especially valuable in larger applications and API projects built with FastAPI.
yieldGenerators produce values lazily, one at a time, instead of constructing an entire collection in memory.
def count_up_to(limit: int):for number in range(1, limit + 1):yield numberfor number in count_up_to(3):print(number)
Output:
123
Unlike return, which exits a function, yield produces a value and suspends execution. The generator resumes when the next value is requested.
Generators are useful for processing large files, streaming data, and building memory-efficient pipelines.
Understanding Python functions goes beyond knowing how to define and call them.
These concepts form an important foundation for Python backend development, particularly when building APIs, reusable utilities, and data-processing pipelines.