mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 21:51:50 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			251 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			251 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
| .. _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
 | |
| ====================================================================
 | |
| 
 | |
| Python 3.10 adds a new function to the standard library:
 | |
| :func:`inspect.get_annotations`.  In Python versions 3.10
 | |
| through 3.13, calling this function is the best practice for
 | |
| accessing the annotations dict of any object that supports
 | |
| annotations.  This function can also "un-stringize"
 | |
| stringized annotations for you.
 | |
| 
 | |
| 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`.
 | |
| 
 | |
| 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.
 | |
| 
 | |
| 
 | |
| Accessing The Annotations Dict Of An Object In Python 3.9 And Older
 | |
| ===================================================================
 | |
| 
 | |
| 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.
 | |
| 
 | |
| 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.
 | |
| 
 | |
| 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::
 | |
| 
 | |
|     class Base:
 | |
|         a: int = 3
 | |
|         b: str = 'abc'
 | |
| 
 | |
|     class Derived(Base):
 | |
|         pass
 | |
| 
 | |
|     print(Derived.__annotations__)
 | |
| 
 | |
| This will print the annotations dict from ``Base``, not
 | |
| ``Derived``.
 | |
| 
 | |
| 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,
 | |
| they are stored in the class's :attr:`~type.__dict__` dictionary.  Since
 | |
| the class may or may not have annotations defined, best practice
 | |
| is to call the :meth:`~dict.get` method on the class dict.
 | |
| 
 | |
| 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::
 | |
| 
 | |
|     if isinstance(o, type):
 | |
|         ann = o.__dict__.get('__annotations__', None)
 | |
|     else:
 | |
|         ann = getattr(o, '__annotations__', None)
 | |
| 
 | |
| 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.
 | |
| 
 | |
| Note that some exotic or malformed type objects may not have
 | |
| a :attr:`~type.__dict__` attribute, so for extra safety you may also wish
 | |
| to use :func:`getattr` to access :attr:`!__dict__`.
 | |
| 
 | |
| 
 | |
| Manually Un-Stringizing Stringized Annotations
 | |
| ==============================================
 | |
| 
 | |
| 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
 | |
|   :attr:`o.__globals__ <function.__globals__>` as the globals when calling
 | |
|   :func:`eval`.
 | |
| 
 | |
| 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.
 | |
| 
 | |
| 
 | |
| Best Practices For ``__annotations__`` In Any Python Version
 | |
| ============================================================
 | |
| 
 | |
| * You should avoid assigning to the ``__annotations__`` member
 | |
|   of objects directly.  Let Python manage setting ``__annotations__``.
 | |
| 
 | |
| * If you do assign directly to the ``__annotations__`` member
 | |
|   of an object, you should always set it to a ``dict`` object.
 | |
| 
 | |
| * 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
 | |
|   of an object, you should ensure that it's a
 | |
|   dictionary before attempting to examine its contents.
 | |
| 
 | |
| * You should avoid modifying ``__annotations__`` dicts.
 | |
| 
 | |
| * You should avoid deleting the ``__annotations__`` attribute
 | |
|   of an object.
 | |
| 
 | |
| 
 | |
| ``__annotations__`` Quirks
 | |
| ==========================
 | |
| 
 | |
| 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.
 | |
| 
 | |
| 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
 | |
| class's :attr:`~type.__dict__`
 | |
| (e.g., ``cls.__dict__.get('__annotations__', None)``).
 | 
