Writing Functions and Modules in Python
Functions make inputs, outputs, and assumptions explicit. Modules group related definitions behind a stable import name.
Function parameters and return values
def rectangle_area(length, width=1):
"""Return the area of a rectangle with positive dimensions."""
if length <= 0 or width <= 0:
raise ValueError("dimensions must be positive")
return length * width
print(rectangle_area(4)) # 4
print(rectangle_area(4, 3)) # 12
Default argument expressions are evaluated once, when def runs. Do not use a mutable object such as [] as a default when calls should not share state. Use None and create a new object inside the function.
def append_reading(value, readings=None):
if readings is None:
readings = []
readings.append(value)
return readings
print(append_reading(3)) # [3]
print(append_reading(4)) # [4], not [3, 4]
Positional and keyword arguments
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dictionary. Use them when the open-ended interface is intentional, not to hide an unclear contract.
def summarize(*values, precision=2):
if not values:
raise ValueError("at least one value is required")
return round(sum(values) / len(values), precision)
print(summarize(1, 2, 6, precision=1)) # 3.0
Exceptions are part of the contract
Raise a specific exception for invalid input and document it. Return values should represent successful results, not switch unpredictably between data and error-message strings.
def ratio(part, whole):
if whole == 0:
raise ValueError("whole must not be zero")
return part / whole
print(ratio(3, 4)) # 0.75
Modules and imports
Every .py file is a module. Suppose metrics.py contains:
# metrics.py
def range_width(values):
return max(values) - min(values)
Code in another file in the same project can import it:
from metrics import range_width
print(range_width([3, 8, 5])) # 5
These two snippets are intentionally manual because they demonstrate a two-file import boundary. Avoid from module import *; explicit names make dependencies visible and prevent accidental collisions.
Packages and script entry points
A package organizes modules under one import namespace. Modern Python supports namespace packages, so __init__.py is not universally required, although regular packages commonly include it and can use it to define their public API.
Code that should run only when a module is executed as a script belongs behind an entry-point guard:
def main():
print("Run the analysis")
if __name__ == "__main__":
main()
Keep import-time work small and predictable. Pass dependencies and data into functions rather than relying on mutable global state; that makes code easier to test and reuse.