| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | .. _annotations-howto:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | **************************
 | 
					
						
							|  |  |  | Annotations Best Practices
 | 
					
						
							|  |  |  | **************************
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | :author: Larry Hastings
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | .. topic:: Abstract
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   This document is designed to encapsulate the best practices
 | 
					
						
							|  |  |  |   for working with annotations dicts.  If you write Python code
 | 
					
						
							|  |  |  |   that examines ``__annotations__`` on Python objects, we
 | 
					
						
							|  |  |  |   encourage you to follow the guidelines described below.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   The document is organized into four sections:
 | 
					
						
							|  |  |  |   best practices for accessing the annotations of an object
 | 
					
						
							|  |  |  |   in Python versions 3.10 and newer,
 | 
					
						
							|  |  |  |   best practices for accessing the annotations of an object
 | 
					
						
							|  |  |  |   in Python versions 3.9 and older,
 | 
					
						
							|  |  |  |   other best practices
 | 
					
						
							|  |  |  |   for ``__annotations__`` that apply to any Python version,
 | 
					
						
							|  |  |  |   and
 | 
					
						
							|  |  |  |   quirks of ``__annotations__``.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Note that this document is specifically about working with
 | 
					
						
							|  |  |  |   ``__annotations__``, not uses *for* annotations.
 | 
					
						
							|  |  |  |   If you're looking for information on how to use "type hints"
 | 
					
						
							|  |  |  |   in your code, please see the :mod:`typing` module.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Accessing The Annotations Dict Of An Object In Python 3.10 And Newer
 | 
					
						
							|  |  |  | ====================================================================
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | Python 3.10 adds a new function to the standard library:
 | 
					
						
							|  |  |  | :func:`inspect.get_annotations`.  In Python versions 3.10
 | 
					
						
							| 
									
										
										
										
											2024-09-11 07:49:59 -07:00
										 |  |  | through 3.13, calling this function is the best practice for
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | accessing the annotations dict of any object that supports
 | 
					
						
							|  |  |  | annotations.  This function can also "un-stringize"
 | 
					
						
							|  |  |  | stringized annotations for you.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-11 07:49:59 -07:00
										 |  |  | In Python 3.14, there is a new :mod:`annotationlib` module
 | 
					
						
							|  |  |  | with functionality for working with annotations. This
 | 
					
						
							|  |  |  | includes a :func:`annotationlib.get_annotations` function,
 | 
					
						
							|  |  |  | which supersedes :func:`inspect.get_annotations`.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | If for some reason :func:`inspect.get_annotations` isn't
 | 
					
						
							|  |  |  | viable for your use case, you may access the
 | 
					
						
							|  |  |  | ``__annotations__`` data member manually.  Best practice
 | 
					
						
							|  |  |  | for this changed in Python 3.10 as well: as of Python 3.10,
 | 
					
						
							|  |  |  | ``o.__annotations__`` is guaranteed to *always* work
 | 
					
						
							|  |  |  | on Python functions, classes, and modules.  If you're
 | 
					
						
							|  |  |  | certain the object you're examining is one of these three
 | 
					
						
							|  |  |  | *specific* objects, you may simply use ``o.__annotations__``
 | 
					
						
							|  |  |  | to get at the object's annotations dict.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | However, other types of callables--for example,
 | 
					
						
							|  |  |  | callables created by :func:`functools.partial`--may
 | 
					
						
							|  |  |  | not have an ``__annotations__`` attribute defined.  When
 | 
					
						
							|  |  |  | accessing the ``__annotations__`` of a possibly unknown
 | 
					
						
							|  |  |  | object,  best practice in Python versions 3.10 and
 | 
					
						
							|  |  |  | newer is to call :func:`getattr` with three arguments,
 | 
					
						
							|  |  |  | for example ``getattr(o, '__annotations__', None)``.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Before Python 3.10, accessing ``__annotations__`` on a class that
 | 
					
						
							|  |  |  | defines no annotations but that has a parent class with
 | 
					
						
							|  |  |  | annotations would return the parent's ``__annotations__``.
 | 
					
						
							|  |  |  | In Python 3.10 and newer, the child class's annotations
 | 
					
						
							|  |  |  | will be an empty dict instead.
 | 
					
						
							| 
									
										
										
										
											2022-12-24 21:07:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | Accessing The Annotations Dict Of An Object In Python 3.9 And Older
 | 
					
						
							|  |  |  | ===================================================================
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | In Python 3.9 and older, accessing the annotations dict
 | 
					
						
							|  |  |  | of an object is much more complicated than in newer versions.
 | 
					
						
							|  |  |  | The problem is a design flaw in these older versions of Python,
 | 
					
						
							|  |  |  | specifically to do with class annotations.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | Best practice for accessing the annotations dict of other
 | 
					
						
							|  |  |  | objects--functions, other callables, and modules--is the same
 | 
					
						
							|  |  |  | as best practice for 3.10, assuming you aren't calling
 | 
					
						
							|  |  |  | :func:`inspect.get_annotations`: you should use three-argument
 | 
					
						
							|  |  |  | :func:`getattr` to access the object's ``__annotations__``
 | 
					
						
							|  |  |  | attribute.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | Unfortunately, this isn't best practice for classes.  The problem
 | 
					
						
							|  |  |  | is that, since ``__annotations__`` is optional on classes, and
 | 
					
						
							|  |  |  | because classes can inherit attributes from their base classes,
 | 
					
						
							|  |  |  | accessing the ``__annotations__`` attribute of a class may
 | 
					
						
							|  |  |  | inadvertently return the annotations dict of a *base class.*
 | 
					
						
							|  |  |  | As an example::
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  |     class Base:
 | 
					
						
							|  |  |  |         a: int = 3
 | 
					
						
							|  |  |  |         b: str = 'abc'
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  |     class Derived(Base):
 | 
					
						
							|  |  |  |         pass
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  |     print(Derived.__annotations__)
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | This will print the annotations dict from ``Base``, not
 | 
					
						
							|  |  |  | ``Derived``.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | Your code will have to have a separate code path if the object
 | 
					
						
							|  |  |  | you're examining is a class (``isinstance(o, type)``).
 | 
					
						
							|  |  |  | In that case, best practice relies on an implementation detail
 | 
					
						
							|  |  |  | of Python 3.9 and before: if a class has annotations defined,
 | 
					
						
							| 
									
										
										
										
											2024-09-25 12:29:58 -07:00
										 |  |  | they are stored in the class's :attr:`~type.__dict__` dictionary.  Since
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | the class may or may not have annotations defined, best practice
 | 
					
						
							| 
									
										
										
										
											2024-09-25 12:29:58 -07:00
										 |  |  | is to call the :meth:`~dict.get` method on the class dict.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | To put it all together, here is some sample code that safely
 | 
					
						
							|  |  |  | accesses the ``__annotations__`` attribute on an arbitrary
 | 
					
						
							|  |  |  | object in Python 3.9 and before::
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  |     if isinstance(o, type):
 | 
					
						
							|  |  |  |         ann = o.__dict__.get('__annotations__', None)
 | 
					
						
							|  |  |  |     else:
 | 
					
						
							|  |  |  |         ann = getattr(o, '__annotations__', None)
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | After running this code, ``ann`` should be either a
 | 
					
						
							|  |  |  | dictionary or ``None``.  You're encouraged to double-check
 | 
					
						
							|  |  |  | the type of ``ann`` using :func:`isinstance` before further
 | 
					
						
							|  |  |  | examination.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | Note that some exotic or malformed type objects may not have
 | 
					
						
							| 
									
										
										
										
											2024-09-25 12:29:58 -07:00
										 |  |  | a :attr:`~type.__dict__` attribute, so for extra safety you may also wish
 | 
					
						
							|  |  |  | to use :func:`getattr` to access :attr:`!__dict__`.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Manually Un-Stringizing Stringized Annotations
 | 
					
						
							|  |  |  | ==============================================
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | In situations where some annotations may be "stringized",
 | 
					
						
							|  |  |  | and you wish to evaluate those strings to produce the
 | 
					
						
							|  |  |  | Python values they represent, it really is best to
 | 
					
						
							|  |  |  | call :func:`inspect.get_annotations` to do this work
 | 
					
						
							|  |  |  | for you.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | If you're using Python 3.9 or older, or if for some reason
 | 
					
						
							|  |  |  | you can't use :func:`inspect.get_annotations`, you'll need
 | 
					
						
							|  |  |  | to duplicate its logic.  You're encouraged to examine the
 | 
					
						
							|  |  |  | implementation of :func:`inspect.get_annotations` in the
 | 
					
						
							|  |  |  | current Python version and follow a similar approach.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | In a nutshell, if you wish to evaluate a stringized annotation
 | 
					
						
							|  |  |  | on an arbitrary object ``o``:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | * If ``o`` is a module, use ``o.__dict__`` as the
 | 
					
						
							|  |  |  |   ``globals`` when calling :func:`eval`.
 | 
					
						
							|  |  |  | * If ``o`` is a class, use ``sys.modules[o.__module__].__dict__``
 | 
					
						
							|  |  |  |   as the ``globals``, and ``dict(vars(o))`` as the ``locals``,
 | 
					
						
							|  |  |  |   when calling :func:`eval`.
 | 
					
						
							|  |  |  | * If ``o`` is a wrapped callable using :func:`functools.update_wrapper`,
 | 
					
						
							|  |  |  |   :func:`functools.wraps`, or :func:`functools.partial`, iteratively
 | 
					
						
							|  |  |  |   unwrap it by accessing either ``o.__wrapped__`` or ``o.func`` as
 | 
					
						
							|  |  |  |   appropriate, until you have found the root unwrapped function.
 | 
					
						
							|  |  |  | * If ``o`` is a callable (but not a class), use
 | 
					
						
							| 
									
										
										
										
											2023-12-11 10:00:42 +00:00
										 |  |  |   :attr:`o.__globals__ <function.__globals__>` as the globals when calling
 | 
					
						
							|  |  |  |   :func:`eval`.
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | However, not all string values used as annotations can
 | 
					
						
							|  |  |  | be successfully turned into Python values by :func:`eval`.
 | 
					
						
							|  |  |  | String values could theoretically contain any valid string,
 | 
					
						
							|  |  |  | and in practice there are valid use cases for type hints that
 | 
					
						
							|  |  |  | require annotating with string values that specifically
 | 
					
						
							|  |  |  | *can't* be evaluated.  For example:
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | * :pep:`604` union types using ``|``, before support for this
 | 
					
						
							|  |  |  |   was added to Python 3.10.
 | 
					
						
							|  |  |  | * Definitions that aren't needed at runtime, only imported
 | 
					
						
							|  |  |  |   when :const:`typing.TYPE_CHECKING` is true.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | If :func:`eval` attempts to evaluate such values, it will
 | 
					
						
							|  |  |  | fail and raise an exception.  So, when designing a library
 | 
					
						
							|  |  |  | API that works with annotations, it's recommended to only
 | 
					
						
							|  |  |  | attempt to evaluate string values when explicitly requested
 | 
					
						
							|  |  |  | to by the caller.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Best Practices For ``__annotations__`` In Any Python Version
 | 
					
						
							|  |  |  | ============================================================
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | * You should avoid assigning to the ``__annotations__`` member
 | 
					
						
							|  |  |  |   of objects directly.  Let Python manage setting ``__annotations__``.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | * If you do assign directly to the ``__annotations__`` member
 | 
					
						
							|  |  |  |   of an object, you should always set it to a ``dict`` object.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-11 07:49:59 -07:00
										 |  |  | * You should avoid accessing ``__annotations__`` directly on any object.
 | 
					
						
							|  |  |  |   Instead, use :func:`annotationlib.get_annotations` (Python 3.14+)
 | 
					
						
							|  |  |  |   or :func:`inspect.get_annotations` (Python 3.10+).
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | * If you do directly access the ``__annotations__`` member
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  |   of an object, you should ensure that it's a
 | 
					
						
							|  |  |  |   dictionary before attempting to examine its contents.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | * You should avoid modifying ``__annotations__`` dicts.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | * You should avoid deleting the ``__annotations__`` attribute
 | 
					
						
							|  |  |  |   of an object.
 | 
					
						
							| 
									
										
										
										
											2021-05-01 21:19:24 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ``__annotations__`` Quirks
 | 
					
						
							|  |  |  | ==========================
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-05 13:28:28 +02:00
										 |  |  | In all versions of Python 3, function
 | 
					
						
							|  |  |  | objects lazy-create an annotations dict if no annotations
 | 
					
						
							|  |  |  | are defined on that object.  You can delete the ``__annotations__``
 | 
					
						
							|  |  |  | attribute using ``del fn.__annotations__``, but if you then
 | 
					
						
							|  |  |  | access ``fn.__annotations__`` the object will create a new empty dict
 | 
					
						
							|  |  |  | that it will store and return as its annotations.  Deleting the
 | 
					
						
							|  |  |  | annotations on a function before it has lazily created its annotations
 | 
					
						
							|  |  |  | dict will throw an ``AttributeError``; using ``del fn.__annotations__``
 | 
					
						
							|  |  |  | twice in a row is guaranteed to always throw an ``AttributeError``.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Everything in the above paragraph also applies to class and module
 | 
					
						
							|  |  |  | objects in Python 3.10 and newer.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | In all versions of Python 3, you can set ``__annotations__``
 | 
					
						
							|  |  |  | on a function object to ``None``.  However, subsequently
 | 
					
						
							|  |  |  | accessing the annotations on that object using ``fn.__annotations__``
 | 
					
						
							|  |  |  | will lazy-create an empty dictionary as per the first paragraph of
 | 
					
						
							|  |  |  | this section.  This is *not* true of modules and classes, in any Python
 | 
					
						
							|  |  |  | version; those objects permit setting ``__annotations__`` to any
 | 
					
						
							|  |  |  | Python value, and will retain whatever value is set.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | If Python stringizes your annotations for you
 | 
					
						
							|  |  |  | (using ``from __future__ import annotations``), and you
 | 
					
						
							|  |  |  | specify a string as an annotation, the string will
 | 
					
						
							|  |  |  | itself be quoted.  In effect the annotation is quoted
 | 
					
						
							|  |  |  | *twice.*  For example::
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |      from __future__ import annotations
 | 
					
						
							|  |  |  |      def foo(a: "str"): pass
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |      print(foo.__annotations__)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This prints ``{'a': "'str'"}``.  This shouldn't really be considered
 | 
					
						
							|  |  |  | a "quirk"; it's mentioned here simply because it might be surprising.
 | 
					
						
							| 
									
										
										
										
											2024-09-11 07:49:59 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | If you use a class with a custom metaclass and access ``__annotations__``
 | 
					
						
							|  |  |  | on the class, you may observe unexpected behavior; see
 | 
					
						
							|  |  |  | :pep:`749 <749#pep749-metaclasses>` for some examples. You can avoid these
 | 
					
						
							|  |  |  | quirks by using :func:`annotationlib.get_annotations` on Python 3.14+ or
 | 
					
						
							|  |  |  | :func:`inspect.get_annotations` on Python 3.10+. On earlier versions of
 | 
					
						
							|  |  |  | Python, you can avoid these bugs by accessing the annotations from the
 | 
					
						
							| 
									
										
										
										
											2024-09-25 12:29:58 -07:00
										 |  |  | class's :attr:`~type.__dict__`
 | 
					
						
							|  |  |  | (e.g., ``cls.__dict__.get('__annotations__', None)``).
 |