Python annotations: What Is Stored Inside?
In Python, the __annotations__ attribute is a dictionary
that records type hints and metadata explicitly declared on functions,
classes, and modules. This article details the exact data stored within
the __annotations__ dictionary for both functions and
classes, how keys and values are represented, and how runtime evaluation
affects its contents.
Function Annotations
When defined on a function, the __annotations__
dictionary maps parameter names to their declared type annotations,
along with a dedicated key for the return type.
- Parameter Annotations: Each parameter declared with a type hint becomes a key in the dictionary, represented as a string matching the parameter name. The corresponding value is the evaluated type expression. Parameters without annotations are omitted entirely.
- Return Annotations: The return type annotation is
mapped to the special string key
'return'. If no return type is specified, the'return'key is absent from the dictionary. - Special Arguments: Variable-length positional
arguments (
*args) and keyword arguments (**kwargs) are also included as keys using their identifier names (without the asterisks) if they carry type hints.
For example, consider the following function:
def calculate_tax(subtotal: float, discount: float = 0.0, *items: str) -> float:
return subtotal - discountAccessing calculate_tax.__annotations__ yields:
{
'subtotal': <class 'float'>,
'discount': <class 'float'>,
'items': <class 'str'>,
'return': <class 'float'>
}Class Annotations
When defined on a class, __annotations__ stores variable
annotations declared within the class body.
- Class-Level Attributes: Any variable given a type annotation in the class scope is added as a string key with its corresponding evaluated type as the value.
- No Values Required: A variable does not need to be
assigned a value to appear in
__annotations__. Simple statements such asname: strregister'name'in__annotations__without assigning an attribute on the class itself. - Instance Scope Exclusion: Annotations declared
exclusively inside methods (such as
self.attribute: intinside__init__) are not stored in the class's__annotations__dictionary.
For example, in the following class:
class User:
username: str
user_id: int
is_active: bool = True
def __init__(self, role: str):
self.role: str = roleAccessing User.__annotations__ contains only the
class-body attributes:
{
'username': <class 'str'>,
'user_id': <class 'int'>,
'is_active': <class 'bool'>
}The self.role declaration inside __init__
is an instance-level attribute assignment and is not recorded in
User.__annotations__.
Evaluated Objects vs. Strings
By default, the values inside __annotations__ are
references to the actual Python objects (such as
<class 'int'>, typing.List, or custom
classes). However, two exceptions alter this behavior:
- Forward References: If a type annotation is wrapped
in quotes (e.g.,
'User'), the value stored in the dictionary remains a raw string literal. - Deferred Evaluation: When
from __future__ import annotationsis enabled (PEP 563), all annotations are automatically converted into strings at definition time rather than evaluated objects. This avoids circular import issues and reduces runtime overhead.
To safely resolve stringified annotations back to their underlying
types at runtime, Python 3.10 and later provides
inspect.get_annotations(obj), which standardizes accessing
and evaluating the contents of __annotations__.