Python Central HubSign in

Python Programming / Python Function

Function Annotations in Python

Learn about function annotations in Python. Explore the syntax, use cases, and best practices associated with function annotations in Python.

diagram annotations are recorded, not checked mermaid
A type annotation changes nothing about how the function runs. Python stores it and moves on -- there is no conversion, no validation and no error. The value comes from the tools that read those annotations: a type checker before you run, an editor while you type, and libraries that choose to act on them at runtime.

Unraveling Function Annotations in Python: A Comprehensive Guide

Function annotations in Python provide a way to attach metadata, including type hints, to the parameters and return values of functions. Introduced in PEP 3107 and enhanced in subsequent PEPs (Python Enhancement Proposals), function annotations offer a mechanism for developers to provide additional information about the expected types and purpose of function parameters and return values. In this comprehensive guide, we'll explore the syntax, use cases, and best practices associated with function annotations in Python.

Basic Syntax of Function Annotations

The syntax for function annotations involves using colons (:) after the parameter or return value name, followed by the annotation expression. Annotations can be any valid expressions, but they are commonly used for type hints.

Python
def greet(name: str) -> str:
    return f"Hello, {name}!"

In this example, the name parameter is annotated with the type hint str, indicating that the expected type for name is a string. The return value of the function is also annotated with str, signifying that the function is expected to return a string.

Type Hints in Function Annotations

Type hints, introduced in PEP 484, are a major use case for function annotations. They allow developers to indicate the expected types of function parameters and return values, providing clarity and enabling static type checking tools.

Python
def add_numbers(a: int, b: int) -> int:
    return a + b

In this example, the add_numbers function takes two parameters, a and b, both annotated with the type hint int, and returns an int. This information is valuable for both developers and tools that perform static analysis.

Default Values and Type Hints

Function annotations can also be combined with default parameter values:

Python
def greet(name: str = "Guest") -> str:
    return f"Hello, {name}!"

Here, the name parameter is annotated with the type hint str, and it has a default value of "Guest." The function is expected to return a string.

Annotations for Complex Types

Function annotations are not limited to simple types; they can also be used for more complex types and custom classes:

Python
from typing import List, Tuple
 
def process_data(data: List[Tuple[str, int]]) -> List[str]:
    result = [f"{name}: {score}" for name, score in data]
    return result

In this example, the data parameter is annotated with the type hint List[Tuple[str, int]], indicating that it's expected to be a list of tuples where the first element is a string and the second element is an integer. The function is expected to return a list of strings.

Using None in Function Annotations

When a function parameter or return value can be of any type, the None type can be used:

Python
def print_message(message: str, times: int) -> None:
    for _ in range(times):
        print(message)

Here, the print_message function takes a message parameter of type str and a times parameter of type int. The function does not return any value (None).

Function Annotations for Multiple Return Values

Function annotations can also be used for functions that return multiple values:

Python
def get_user_info() -> Tuple[str, int]:
    name = "John"
    age = 30
    return name, age

In this example, the get_user_info function returns a tuple containing the user's name and age. The function is annotated with the type hint Tuple[str, int], indicating that it's expected to return a tuple where the first element is a string and the second element is an integer.

Custom Type Annotations

In addition to the built-in types, developers can also use custom types in function annotations:

Python
from typing import List
 
User = List[str]
def get_users() -> List[User]:
    ...

Here, we define a custom type User that is a list of strings. The get_users function is annotated with the type hint List[User], indicating that it's expected to return a list of User objects.

Dir and Annotations

The dir function can be used to access the annotations for a function:

Python
def greet(name: str) -> str:
    return f"Hello, {name}!"
 
print(dir(greet))

Output:

cmd
C:\Users\Your Name> python function.py
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__original_wrapped__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__', '__wrapped__']

In this example, we use the dir function to access the annotations for the greet function. The annotations are stored in the __annotations__ attribute, which is a dictionary containing the annotations for each parameter and the return value.

Function Annotations and Docstrings

Function annotations can be used in conjunction with docstrings to provide comprehensive documentation for functions:

Python
def greet(name: str) -> str:
    """Returns a greeting for the given name"""
    return f"Hello, {name}!"
 
print(greet.__doc__)
print(greet.__annotations__)

Output:

cmd
C:\Users\Your Name> python function.py
Returns a greeting for the given name
{'name': <class 'str'>, 'return': <class 'str'>}

In this example, we define a greet function that takes a name parameter of type str and returns a string. The docstring for the function provides additional information about the purpose of the function. The annotations for the function are stored in the __annotations__ attribute, which is a dictionary containing the annotations for each parameter and the return value.

Best Practices for Function Annotations

  1. Use Type Hints for Readability: Type hints improve code readability and provide clear expectations for function parameters and return values.

  2. Be Consistent: Adopt a consistent style for function annotations throughout your codebase to maintain clarity.

  3. Use Descriptive Variable Names: Choose meaningful variable names and annotate them appropriately to convey the purpose of each parameter or return value.

  4. Consider Using Type Hints for Return Values: Type hints for return values can be particularly helpful for understanding the expected output of a function.

  5. Leverage Type Checking Tools: Take advantage of static type checking tools like mypy to catch potential type-related errors early in the development process.

  6. Use Type Hints with Docstrings: Combine type hints with docstrings to provide comprehensive documentation for your functions.

Limitations of Function Annotations

While function annotations offer valuable benefits, it's essential to note that they are optional and do not enforce type checking at runtime. They primarily serve as a form of documentation and support static type checking tools.

Conclusion

Function annotations in Python empower developers to provide additional information about function parameters and return values, enhancing code clarity and enabling static type checking. By incorporating type hints into function annotations, developers can communicate their intentions more effectively, making it easier for both humans and tools to understand and analyze code. As you explore Python programming, consider integrating function annotations into your coding practices to improve code readability and maintainability. For more insights and practical examples, check out our tutorials on Python Central Hub!


sketch When an annotation is evaluated changed in Python 3.14 p5.js
Before 3.14, an annotation was evaluated as the def line ran, so naming a class that did not exist yet raised immediately -- which is why forward references had to be quoted. PEP 649 made evaluation lazy: the definition is accepted and the error, if any, arrives only when something reads the annotations. Both behaviours were measured on this machine.

Check yourself

quizCheck yourself
  1. `def add(a: int, b: int) -> int`. What does `add('x', 'y')` return?

    Show answer

    B — `'xy'` — Verified. The interpreter records annotations and never consults them, so the body simply concatenated the strings — returning a `str` from a function annotated `-> int`.

  2. On Python 3.14, `def f(x: NotDefinedYet)` where that name does not exist. What happens?

    Show answer

    B — Accepted; NameError only when `__annotations__` is read — PEP 649 made annotation evaluation lazy in 3.14. Measured on both interpreters here: 3.11 raised at the def line, 3.14 accepted it.

  3. Why were forward references traditionally written as strings?

    Show answer

    B — Because the annotation was evaluated at definition time, before the class existed — A method annotated `-> Node` inside `class Node` referred to a name that did not exist yet. Quoting deferred the evaluation; from 3.14 that is the default.

  4. What is `get_type_hints(f)` for?

    Show answer

    B — Resolving string annotations into real type objects — Annotations may be stored as strings. `get_type_hints` resolves them, and `get_origin`/`get_args` then decompose a generic such as `list[int]` into `list` and `(int,)`.

Try it: Function Annotations Exercises

Exercise 1 – Type Hints

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Exercise 2 – Annotations with Default

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Exercise 3 – View Annotations

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Was this page helpful?

Let us know how we did