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
defline 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.
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.
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.
print(area("ab", 3)) # 'ababab': str * int is repeat, no error
x: int = "not an int"
print(x) # runs fine04Docstring 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).
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.
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
Read help() for built-ins
Run
help(len)andhelp(sorted)in the editor. Compare the signature and the first docstring line with what you learned about/and*in Topic 5.4. - 2
Add a hint and a docstring to your own function
Take
make_teafrom Topic 5.2 and add hints (cups: int = 1, sugar: int = 0, milk: bool = False) and a one-line docstring. Printmake_tea.__annotations__. - 3
Lie to Python
Call
area("ab", 3)and thenarea("ab", "cd"). The first runs; the second fails. The failure comes from*between two strings, not from the hints.
Code & diagrams
Annotations are shown by help(), stored in __annotations__ and not enforced: the last call multiplies a string.
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.
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.
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'>}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.
def double(n: int) -> int:
return n * 2
print(double("ab"))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
defruns (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 atdeftime. The older workaround,from __future__ import annotations, turns every annotation into a string instead. Because the format can differ, read hints withannotationlib.get_annotations(3.14) ortyping.get_type_hintsrather than assuming__annotations__holds real classes. This course's runnable examples use Python 3.13, where annotations are still evaluated immediately. - ▸
inspect.getdoccleans 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-OOflag, 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 handleNone. - ▸
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
doctestmodule) run the examples inside docstrings as tests. They keep documentation honest for small pure functions.
Remember this
- 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
Common longer styles list the parameters and the return value: Google style (
Args:andReturns: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
A type hint (or annotation) says what type something should be. In a
defline,width: floatannotates a parameter and-> floatannotates 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
Python does not enforce hints.
area("ab", 3)runs happily and returns'ababab', even thoughwidth: floatwas 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
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
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
What is a docstring and where does Python store it?
What do type hints do at run time?
Why write type hints if Python does not enforce them?
How can code read a function's hints, and why does that matter for AI tools?
Practice
Add a docstring and hints to def volume(length, width, height): return length * width * height. Print volume.__doc__ and volume.__annotations__.
Write describe_person(name: str, age: int = 0) -> str with a one-line docstring. Use inspect.signature to print its signature.
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__andinspect.signature.Done when you can say why tools and models care about names, hints and docstrings.