Python Docstrings and Type Hints: Complete Guide
Professional Python code requires more than logic—it requires clarity. Docstrings and type hints are the two cornerstones of writing self-documenting functions that your team (and your future self) can understand instantly. Docstrings explain what and why, while type hints explain what type of data flows through your code. Together, they enable IDE autocomplete, catch bugs before runtime, and make your code 40% easier to maintain (according to a 2025 developer survey). This guide shows you how to combine both for production-grade Python.
Writing code that works is half the battle. Making code that others can understand six months from now is the other half.
Key Takeaways
- Docstrings are runtime-accessible documentation strings, different from comments, accessed via
function.__doc__ - Google-style docstrings provide a standard, readable format with Args, Returns, and Examples sections
- Type hints annotate parameter and return value types, enabling static checkers like
mypyto catch errors before execution - Combine docstrings and type hints: let the signature specify types, let the docstring explain purpose
- The
typingmodule providesList,Dict,Optional,Union, and other advanced type constructs
Prerequisites
This article builds on earlier concepts. You should understand:
- Defining functions with parameters and return values
- Python's basic data types:
str,int,float,list,dict,bool - The concept of scope and how variables are passed to functions
What Are Docstrings and How Do They Differ from Comments?
A docstring (documentation string) is a string literal placed as the first statement in a module, function, class, or method. Unlike regular comments, docstrings are attached to Python objects at runtime and can be programmatically accessed.
Comments vs. Docstrings:
- Comments (using
#) are for developers reading source code; the interpreter ignores them - Docstrings (using
"""...""") are for code users; they're stored as the__doc__attribute
This distinction matters: tools like IDEs, Sphinx, and documentation generators automatically extract docstrings to create help text and API docs.
def my_function():
"""This is a docstring. It explains the function's purpose."""
# This is a comment. It's for implementation notes.
pass
print(my_function.__doc__)
# Output: This is a docstring. It explains the function's purpose.
When you hover over my_function in VS Code or call help(my_function) in the Python shell, you see the docstring—never the comments.
How Do You Write Professional Docstrings?
The most widely adopted standard in the Python community is Google Style, which balances readability with structure. It's used by Google, Kubernetes, and thousands of open-source projects.
Single-Line Docstrings
For trivial functions, a one-line summary suffices. Always end with a period and keep it under 79 characters:
def add(a, b):
"""Adds two numbers together."""
return a + b
Multi-Line Docstrings (Google Style)
For anything more complex, use sections to organize information clearly:
def calculate_sale_price(original_price, discount_percent, is_member):
"""Calculates the final price after applying a discount.
A special additional discount is applied for members.
Args:
original_price: The starting price of the item.
discount_percent: The standard discount percentage (e.g., 20.0 for 20%).
is_member: True if the customer is a loyalty member.
Returns:
The final calculated price after all discounts.
Example:
>>> calculate_sale_price(100.0, 20.0, False)
80.0
>>> calculate_sale_price(100.0, 20.0, True)
75.0
"""
discount = original_price * (discount_percent / 100)
if is_member:
discount += original_price * 0.05 # Extra 5% for members
return original_price - discount
Google Style sections:
- Summary line: One sentence describing the function's purpose
- Extended description (optional): Additional details about behavior or side effects
- Args: Each parameter name and its description
- Returns: Description of the return value
- Raises (optional): Exceptions the function may raise
- Example (optional): A runnable usage example
This format is so standard that tools automatically parse it to generate API documentation.
What Are Type Hints and Why Should You Use Them?
Python is dynamically typed, meaning you don't declare types—they're inferred at runtime. This flexibility comes at a cost: type errors only appear when the wrong type is used, sometimes deep in execution. Type hints (annotations) specify expected types without enforcing them, enabling static analysis tools to catch errors before you run the code.
Type hints were introduced in Python 3.5 (PEP 484). They have zero runtime cost—Python ignores them at execution—but immense value for tooling.
def add(a: int, b: int) -> int:
"""Adds two numbers together."""
return a + b
add(5, 10) # Correct: int + int → int
add("a", "b") # Type checker warns: str is not int
A type checker like mypy analyzes this and warns about the second call without running it. Your IDE also uses hints to provide autocomplete and inline error squiggles.
Type Hint Syntax
For parameters: parameter_name: type
For return values: -> type
def greet(name: str) -> str:
"""Returns a greeting message."""
return f"Hello, {name}!"
How Do You Use Advanced Types from the typing Module?
For complex types beyond int, str, and bool, the typing module provides generic types. These are especially valuable for collections and optional values.
List, Dict, Set, Tuple
Specify the types of items in collections:
from typing import List, Dict
def process_names(names: List[str]) -> Dict[str, int]:
"""Returns a dict mapping each name to its character count."""
return {name: len(name) for name in names}
This tells developers and type checkers: expect a list of strings; return a dict with string keys and integer values.
Optional for Nullable Values
If a value can be None, use Optional[T] (equivalent to Union[T, None]):
from typing import Optional
def get_user_age(user_id: int) -> Optional[int]:
"""Returns the age if found, None otherwise."""
# Function logic here
return None # Valid because return type is Optional[int]
Union for Multiple Types
When a value can be one of several types:
from typing import Union
def parse_value(data: Union[str, int]) -> str:
"""Converts a string or int to string."""
return str(data)
Complete Example: Combining Docstrings and Type Hints
from typing import Optional, List
def create_greeting(name: str, age: Optional[int] = None) -> str:
"""Creates a personalized greeting message.
If age is provided, includes it in the greeting. Otherwise, omits it.
Args:
name: The person's name.
age: Optional age. Defaults to None.
Returns:
A personalized greeting string.
Example:
>>> create_greeting("Alice", 30)
'Hello Alice, you are 30 years old!'
>>> create_greeting("Bob")
'Hello Bob!'
"""
if age is not None:
return f"Hello {name}, you are {age} years old!"
return f"Hello {name}!"
Note: The type hints in the signature are the source of truth for types. The docstring's Args section focuses on what each parameter means, not what type it is—the signature already states that.
Frequently Asked Questions
Do type hints slow down my code?
No. Type hints are stripped out before execution and have zero runtime overhead. The only "cost" is the time spent writing them—which pays dividends in reduced bugs and better IDE support.
Should I use from typing import *?
No. Import only what you need: from typing import List, Optional. This keeps your namespace clean and makes it clear which types are in use.
What does Any mean?
Any is a type that disables all type checking for that variable. Use it sparingly—only when you truly cannot specify a type. Tools recommend avoiding it in public APIs.
from typing import Any
def debug_print(value: Any) -> None:
"""Accepts any type; useful for debugging."""
print(value)
Does every function need type hints?
Not strictly. For internal helper functions, hints may be overkill. But for any public API or function others will call, hints are essential. Start with public functions and type hints on complex parameters.
How do I run a static type checker?
Install mypy:
pip install mypy
Then check your code:
mypy my_script.py
It reports all type inconsistencies without running the code.
Further Reading
- Official Python PEP 484: Type Hints
- Real Python: Type Checking in Python
- Real Python: Documenting Python Code
- Google Python Style Guide
Challenge: Refactor the create_greeting function from this article. Add another parameter titles: Optional[List[str]] = None (a list of titles like ["Dr.", "Prof."]), and if provided, prepend the first title to the name in the greeting. Update both the docstring (Google style) and all type hints.
Next article: Our exploration of functions culminates with lambda functions, which allow you to create small, anonymous functions inline. We'll see how they integrate with higher-order functions like map() and filter().
Happy documenting!