Skip to main content

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.