Command Palette

Search for a command to run...

Hectal
PHASE 5Beginner ~25 min· topic 9 of 10

Topic 5.9

Docstrings and Type Hints: A First Look

In one line

A docstring documents what a function does, and type hints (annotations) document what types its parameters and result should be. Python stores both on the function, shows them in help(), and lets tools read them, but it does not enforce the hints when the code runs.

Think of it like this

The label on a medicine box. It says what the medicine is for and how much to take (the docstring), and that the dose is "one tablet" and not "one bottle" (the type hint). The label helps the person who reads it. It does not physically stop anyone from swallowing a whole bottle. Python hints are the same: a label, not a lock.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Docstring
A string at the start of a function that describes it. Stored as __doc__.
Type hint (annotation)
A note that says what type a parameter, variable or result should have.
`->`
The arrow in a def line that introduces the return annotation.
`__annotations__`
The dictionary where Python stores a function's hints.
Type checker
A tool (mypy, Pyright) that reads hints and reports likely type bugs without running the code.
`help()`
A built-in that shows an object's docstring and signature.
Schema
A description of the shape of data, such as names and types of fields.

Step by step

01A docstring and help()

The docstring must be the first statement. help(area) prints the module, the signature (including hints) and the docstring.

main.pywhole filepython
def area(width, height):
    """Return the area of a rectangle.

    Both sides must use the same unit.
    """
    return width * height


print(area.__doc__)
help(area)

02Adding type hints

Write a colon and a type after each parameter, and -> type before the colon of the def line. Defaults come after the hint: units: str = "celsius". A space around = is the style when a hint is present.

main.pywhole filepython
def area(width: float, height: float) -> float:
    """Return the area of a rectangle."""
    return width * height


print(area(3, 4.5))
print(area.__annotations__)

03Hints are not enforced

Python stores the annotations and does nothing with them. A call with the wrong types runs, and may give a strange answer or an error somewhere deeper. A type checker would flag area("ab", 3) before the program runs. The same goes for annotated variables: x: int = "hello" is accepted at run time.

main.pywhole filepython
print(area("ab", 3))      # 'ababab': str * int is repeat, no error
x: int = "not an int"
print(x)                  # runs fine

04Docstring styles

A one-liner is fine for simple functions. For public functions with several parameters, list them. Pick one style for the whole project (Google style is common).

main.pywhole filepython
def get_weather(city: str, units: str = "celsius") -> str:
    """Return a short weather report for a city.

    Args:
        city: The city name, for example "Pune".
        units: "celsius" or "fahrenheit".

    Returns:
        A one-line report.
    """
    return f"Sunny in {city} ({units})"

05Reading hints with code

Because Python stores hints, other code can read them. inspect.signature gives the parameters with their annotations and defaults. A few lines of code can turn any well-annotated function into a schema, which is what tool-calling libraries do for you.

Reading hints with codediagram
Rendering diagram…

06What to write, and what to leave out

Do not repeat the code in words ("adds a and b" over a + b). Say what and why, mention surprises ("returns None when the list is empty"), and keep hints honest. A wrong hint is worse than no hint, because tools and readers will trust it. Phase 10 covers list[int], X | None, Callable and checking with mypy.

Try it yourself

  1. 1

    Read help() for built-ins

    Run help(len) and help(sorted) in the editor. Compare the signature and the first docstring line with what you learned about / and * in Topic 5.4.

  2. 2

    Add a hint and a docstring to your own function

    Take make_tea from Topic 5.2 and add hints (cups: int = 1, sugar: int = 0, milk: bool = False) and a one-line docstring. Print make_tea.__annotations__.

  3. 3

    Lie to Python

    Call area("ab", 3) and then area("ab", "cd"). The first runs; the second fails. The failure comes from * between two strings, not from the hints.

Code & diagrams

Docstring, annotations and help() New tab

Annotations are shown by help(), stored in __annotations__ and not enforced: the last call multiplies a string.

Sign in to run this example in your browser.

Expected output

13.5
docstring: Return the area of a rectangle.

Both sides must use the same unit.

annotations: {'width': <class 'float'>, 'height': <class 'float'>, 'return': <class 'float'>}
ababab
Help on function area in module __main__:

area(width: float, height: float) -> float
    Return the area of a rectangle.

    Both sides must use the same unit.
Building a tool description from a function New tab

This is the idea behind tool calling: the function's name, docstring and hints become a JSON description. Real libraries do the same with more detail. The two `import` lines load toolboxes that come with Python (Topic 6.1). `inspect.getdoc` returns the docstring text, and `json.dumps(x, indent=2)` prints nested data as indented JSON. The last two lines show that an annotation on a plain variable, like `x: int`, is stored too.

Sign in to run this example in your browser.

Expected output

(city: str, units: str = 'celsius') -> str
{
  "name": "get_weather",
  "description": "Return a short weather report for a city.",
  "parameters": {
    "city": "str",
    "units": "str"
  },
  "required": [
    "city"
  ]
}
not an int - {'x': <class 'int'>}
A Google-style docstringpython

Google style: a summary line, then Args and Returns sections.

def get_weather(city: str, units: str = "celsius") -> str:
    """Return a short weather report for a city.

    Args:
        city: The city name, for example "Pune".
        units: "celsius" or "fahrenheit".

    Returns:
        A one-line report.
    """

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Trust a wrong hint

Declare a parameter as int and pass a string.

main.pywhole filepython
def double(n: int) -> int:
    return n * 2


print(double("ab"))
terminal
$ python main.py
── what you'll see ──
abab

Myth vs fact

Myth

Type hints make Python statically typed.

Fact

Python stays dynamically typed. Hints are metadata; only external tools or libraries act on them.

Myth

A function with hints doesn't need a docstring.

Fact

Hints say what types; the docstring says what the function means and does, including edge cases and units.

Myth

Wrong hints are harmless because Python ignores them.

Fact

Python ignores them, but your teammates, editors and type checkers don't. A wrong hint misleads everyone.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Annotations are evaluated when def runs (before Python 3.14) and stored in __annotations__. Python 3.14 (PEP 649 and 749) defers their evaluation: the compiler stores a hidden function that computes them, and __annotations__ runs it the first time you ask. That removes the need for quoted forward references such as "Node" in many cases, and a hint that names a class defined later no longer fails at def time. The older workaround, from __future__ import annotations, turns every annotation into a string instead. Because the format can differ, read hints with annotationlib.get_annotations (3.14) or typing.get_type_hints rather than assuming __annotations__ holds real classes. This course's runnable examples use Python 3.13, where annotations are still evaluated immediately.

  • ▸

    inspect.getdoc cleans the indentation of a docstring. Before Python 3.13 __doc__ kept the raw indentation of continuation lines; since 3.13 the compiler strips the common leading whitespace itself, so __doc__ is already tidy. Python also removes docstrings entirely with the -OO flag, so never rely on __doc__ for program logic.

  • ▸

    Annotations on local variables inside a function (count: int = 0) are neither evaluated nor stored: only module-level and class-level variable annotations and parameter and return annotations end up in __annotations__. Hints cost nothing at call time. For return types of functions that may produce nothing, annotate honestly (-> str | None, Phase 10) so a type checker forces callers to handle None.

  • ▸

    Pydantic, FastAPI and dataclasses all read annotations at run time. This is why type hints are not optional decoration in modern Python, and why the shape of a hint matters.

  • ▸

    Doctests (the doctest module) run the examples inside docstrings as tests. They keep documentation honest for small pure functions.

Remember this

  1. 1

    A docstring is a piece of text placed as the very first line of a function's body (a module or a class can have one too). Python stores it on the object as __doc__; help(func) and editors show it. Use triple quotes for multi-line ones. Start with a one-line summary written as a command ("Return the area of a rectangle."), then, if needed, a blank line and more detail.

  2. 2

    Common longer styles list the parameters and the return value: Google style (Args: and Returns: sections), NumPy style (underlined sections) and reStructuredText (:param x:). The style matters less than being consistent in a project. Many tools turn docstrings into website documentation. For a small function, one clear line is enough.

  3. 3

    A type hint (or annotation) says what type something should be. In a def line, width: float annotates a parameter and -> float annotates the return value. Variables can be annotated too (count: int = 0). Hints are optional, can use any Python type (int, str, list[int] for a list of ints, dict[str, int] for a dictionary from text to ints), and are stored in the function's __annotations__ dictionary.

  4. 4

    Python does not enforce hints. area("ab", 3) runs happily and returns 'ababab', even though width: float was declared. The hints are information for people and for tools: editors use them for autocomplete and warnings, and type checkers such as mypy and Pyright read them to find bugs before you run the program (Phase 10). Some libraries, like Pydantic, do read them at run time and validate data (Topic 15.4).

  5. 5

    Why start writing hints now? They document the function's contract in a compact, checkable form, they make editors far more helpful, and they are required reading for modern Python: Pydantic models, FastAPI, dataclasses and AI SDKs are all driven by annotations. Start with parameters and return types of public functions, and use the simple types you already know.

  6. 6

    In AI work, a function's name, docstring and annotations become the description a model reads to decide how to call it. Tool-calling libraries build a JSON schema from exactly these: name, parameter names and types, and the docstring (shown below). Writing a clear docstring and honest hints is how you write instructions for a model. Topic 16.10 builds a full tool loop.

Explain it without notes

01

What is a docstring and where does Python store it?

02

What do type hints do at run time?

03

Why write type hints if Python does not enforce them?

04

How can code read a function's hints, and why does that matter for AI tools?

Practice

01

Add a docstring and hints to def volume(length, width, height): return length * width * height. Print volume.__doc__ and volume.__annotations__.

02

Write describe_person(name: str, age: int = 0) -> str with a one-line docstring. Use inspect.signature to print its signature.

03

Show that a hint is not enforced: call a function annotated with int using a string and print the result.

Trade-offs

  • ↔

    Hints cost typing time but save debugging time. On small throw-away scripts they add little. On shared or long-lived code they pay for themselves quickly.

  • ↔

    Long docstrings versus clear code. A docstring that repeats the code goes out of date. Document the contract, the surprises and the units, and let good names carry the rest.

Done when you can

  • Done when you can write a docstring as the first statement and read it with help() and __doc__.

  • Done when you can annotate parameters and the return value of a function.

  • Done when you can explain that Python does not enforce hints, with an example.

  • Done when you can read a function's __annotations__ and inspect.signature.

  • Done when you can say why tools and models care about names, hints and docstrings.