We’ve used type hints in every function since the first lesson of this section. This lesson covers the basics in one place, and introduces docstrings — the way to explain what a function does to anyone who reads it.
Type hints — a recap
A type hint tells a reader (and the type checker) what type a value should be:
def add(a: int, b: int) -> int:
return a + b
name: str = "Manikandan"
ages: list[int] = [25, 30, 35]
Hints go after a colon for variables and parameters, and after -> for return types.
The basic types you’ll use
x: int = 0
x: float = 0.0
x: str = ""
x: bool = True
# collections
x: list[int] = [1, 2, 3]
x: tuple[int, str] = (1, "a")
x: set[str] = {"a", "b"}
x: dict[str, int] = {"a": 1, "b": 2}
# none
x: None = None
Modern Python (3.9+) lets you write list[int] directly. Older tutorials use List[int] imported from typing — you don’t need that anymore.
Union types — when a value can be one of several
Use the | operator to mean “either”:
def parse_int(text: str) -> int | None:
if text.isdigit():
return int(text)
return None
int | None means “returns an int, or None if it fails”. This pattern is so common it has a special name — Optional — but X | None is the modern way to write it.
# all equivalent
x: int | None
x: str | int | float
Type hints for functions that take functions
When a parameter is a function, use Callable:
from collections.abc import Callable
def apply_twice(func: Callable[[int], int], x: int) -> int:
return func(func(x))
def add_one(n: int) -> int:
return n + 1
print(apply_twice(add_one, 5)) # 7
Callable[[int], int] reads as: “a function taking one int and returning an int”. The square brackets describe arguments, then return type.
Don’t worry about memorising this — we’ll cover it more in Section 13. The point is that Python’s type system can describe almost anything you’ll need.
When to write type hints
We’ve said throughout the course: always. Specifically:
- Every function’s parameters and return type
- Module-level variables whose type isn’t obvious
- Collections whose element type can’t be inferred
Local variables inside a function can often skip the hint when their value makes the type obvious:
def main() -> None:
name = "Manikandan" # type is obvious — no hint needed
ages: list[int] = [] # empty list — hint helps the type checker
Docstrings — describing what a function does
A docstring is a string written right after a function’s def line. It documents what the function does, what its parameters mean, and what it returns:
def add(a: int, b: int) -> int:
"""Return the sum of two integers."""
return a + b
Docstrings use triple-quoted strings, so they can span multiple lines.
For more complex functions, a multi-line docstring covers parameters and return value:
def compute_average(numbers: list[float], skip_zeros: bool = False) -> float:
"""
Return the average of a list of numbers.
Args:
numbers: The values to average. Must not be empty.
skip_zeros: If True, exclude zeros from the average.
Returns:
The arithmetic mean as a float.
Raises:
ValueError: If the list is empty (or only zeros, when skip_zeros is True).
"""
if skip_zeros:
numbers = [n for n in numbers if n != 0]
if not numbers:
raise ValueError("Cannot average an empty list")
return sum(numbers) / len(numbers)
Several styles exist (Google style, NumPy style, Sphinx style). The format above is Google style — it’s the most common in modern ML code. Pick a style and stick with it across your project.
Accessing docstrings
A function’s docstring is available at runtime via __doc__:
print(add.__doc__)
# Return the sum of two integers.
The built-in help() function prints them nicely:
help(compute_average)
This is how help() is so useful — every well-documented function carries its own usage info.
When to write docstrings
- Every public function (the ones other parts of the code will call)
- Every function whose name doesn’t already make the behaviour obvious
- Every function that takes more than 2–3 parameters
- Every function that can raise an exception
Skip docstrings for trivial one-liners:
def square(x: int) -> int:
return x * x
The name and type hint already tell you everything.
Summary of Section 5
You can now:
- Define functions with
def - Type-hint parameters and return values
- Use keyword and default arguments
- Handle variable-length arguments with
*argsand**kwargs - Understand scope and avoid mutating globals
- Write lambdas for tiny in-line functions
- Write docstrings that explain what your code does
Functions are the most important building block in any program. Every section from here on will use them heavily.
What’s next
Section 6: Core Data Structures — lists, tuples, sets, and dictionaries. The single most important section in this course for anyone heading into AI or ML.