X Tutup
Skip to content

Commit 49b26fa

Browse files
bpo-43987: Add "Annotations Best Practices" HOWTO doc. (python#25746)
Add "Annotations Best Practices" HOWTO doc.
1 parent 318ca17 commit 49b26fa

File tree

7 files changed

+298
-21
lines changed

7 files changed

+298
-21
lines changed

Doc/glossary.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ Glossary
5757

5858
See :term:`variable annotation`, :term:`function annotation`, :pep:`484`
5959
and :pep:`526`, which describe this functionality.
60+
Also see :ref:`annotations-howto`
61+
for best practices on working with annotations.
6062

6163
argument
6264
A value passed to a :term:`function` (or :term:`method`) when calling the
@@ -455,6 +457,8 @@ Glossary
455457

456458
See :term:`variable annotation` and :pep:`484`,
457459
which describe this functionality.
460+
Also see :ref:`annotations-howto`
461+
for best practices on working with annotations.
458462

459463
__future__
460464
A pseudo-module which programmers can use to enable new language features
@@ -1211,6 +1215,8 @@ Glossary
12111215

12121216
See :term:`function annotation`, :pep:`484`
12131217
and :pep:`526`, which describe this functionality.
1218+
Also see :ref:`annotations-howto`
1219+
for best practices on working with annotations.
12141220

12151221
virtual environment
12161222
A cooperatively isolated runtime environment that allows Python users

Doc/howto/annotations.rst

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
.. _annotations-howto:
2+
3+
**************************
4+
Annotations Best Practices
5+
**************************
6+
7+
:author: Larry Hastings
8+
9+
.. topic:: Abstract
10+
11+
This document is designed to encapsulate the best practices
12+
for working with annotations dicts. If you write Python code
13+
that examines ``__annotations__`` on Python objects, we
14+
encourage you to follow the guidelines described below.
15+
16+
The document is organized into four sections:
17+
best practices for accessing the annotations of an object
18+
in Python versions 3.10 and newer,
19+
best practices for accessing the annotations of an object
20+
in Python versions 3.9 and older,
21+
other best practices
22+
for ``__annotations__`` that apply to any Python version,
23+
and
24+
quirks of ``__annotations__``.
25+
26+
Note that this document is specifically about working with
27+
``__annotations__``, not uses *for* annotations.
28+
If you're looking for information on how to use "type hints"
29+
in your code, please see the :mod:`typing` module.
30+
31+
32+
Accessing The Annotations Dict Of An Object In Python 3.10 And Newer
33+
====================================================================
34+
35+
Python 3.10 adds a new function to the standard library:
36+
:func:`inspect.get_annotations`. In Python versions 3.10
37+
and newer, calling this function is the best practice for
38+
accessing the annotations dict of any object that supports
39+
annotations. This function can also "un-stringize"
40+
stringized annotations for you.
41+
42+
If for some reason :func:`inspect.get_annotations` isn't
43+
viable for your use case, you may access the
44+
``__annotations__`` data member manually. Best practice
45+
for this changed in Python 3.10 as well: as of Python 3.10,
46+
``o.__annotations__`` is guaranteed to *always* work
47+
on Python functions, classes, and modules. If you're
48+
certain the object you're examining is one of these three
49+
*specific* objects, you may simply use ``o.__annotations__``
50+
to get at the object's annotations dict.
51+
52+
However, other types of callables--for example,
53+
callables created by :func:`functools.partial`--may
54+
not have an ``__annotations__`` attribute defined. When
55+
accessing the ``__annotations__`` of a possibly unknown
56+
object, best practice in Python versions 3.10 and
57+
newer is to call :func:`getattr` with three arguments,
58+
for example ``getattr(o, '__annotations__', None)``.
59+
60+
61+
Accessing The Annotations Dict Of An Object In Python 3.9 And Older
62+
===================================================================
63+
64+
In Python 3.9 and older, accessing the annotations dict
65+
of an object is much more complicated than in newer versions.
66+
The problem is a design flaw in these older versions of Python,
67+
specifically to do with class annotations.
68+
69+
Best practice for accessing the annotations dict of other
70+
objects--functions, other callables, and modules--is the same
71+
as best practice for 3.10, assuming you aren't calling
72+
:func:`inspect.get_annotations`: you should use three-argument
73+
:func:`getattr` to access the object's ``__annotations__``
74+
attribute.
75+
76+
Unfortunately, this isn't best practice for classes. The problem
77+
is that, since ``__annotations__`` is optional on classes, and
78+
because classes can inherit attributes from their base classes,
79+
accessing the ``__annotations__`` attribute of a class may
80+
inadvertently return the annotations dict of a *base class.*
81+
As an example::
82+
83+
class Base:
84+
a: int = 3
85+
b: str = 'abc'
86+
87+
class Derived(Base):
88+
pass
89+
90+
print(Derived.__annotations__)
91+
92+
This will print the annotations dict from ``Base``, not
93+
``Derived``.
94+
95+
Your code will have to have a separate code path if the object
96+
you're examining is a class (``isinstance(o, type)``).
97+
In that case, best practice relies on an implementation detail
98+
of Python 3.9 and before: if a class has annotations defined,
99+
they are stored in the class's ``__dict__`` dictionary. Since
100+
the class may or may not have annotations defined, best practice
101+
is to call the ``get`` method on the class dict.
102+
103+
To put it all together, here is some sample code that safely
104+
accesses the ``__annotations__`` attribute on an arbitrary
105+
object in Python 3.9 and before::
106+
107+
if isinstance(o, type):
108+
ann = o.__dict__.get('__annotations__', None)
109+
else:
110+
ann = getattr(o, '__annotations__', None)
111+
112+
After running this code, ``ann`` should be either a
113+
dictionary or ``None``. You're encouraged to double-check
114+
the type of ``ann`` using :func:`isinstance` before further
115+
examination.
116+
117+
Note that some exotic or malformed type objects may not have
118+
a ``__dict__`` attribute, so for extra safety you may also wish
119+
to use :func:`getattr` to access ``__dict__``.
120+
121+
122+
Manually Un-Stringizing Stringized Annotations
123+
==============================================
124+
125+
In situations where some annotations may be "stringized",
126+
and you wish to evaluate those strings to produce the
127+
Python values they represent, it really is best to
128+
call :func:`inspect.get_annotations` to do this work
129+
for you.
130+
131+
If you're using Python 3.9 or older, or if for some reason
132+
you can't use :func:`inspect.get_annotations`, you'll need
133+
to duplicate its logic. You're encouraged to examine the
134+
implementation of :func:`inspect.get_annotations` in the
135+
current Python version and follow a similar approach.
136+
137+
In a nutshell, if you wish to evaluate a stringized annotation
138+
on an arbitrary object ``o``:
139+
140+
* If ``o`` is a module, use ``o.__dict__`` as the
141+
``globals`` when calling :func:`eval`.
142+
* If ``o`` is a class, use ``sys.modules[o.__module__].__dict__``
143+
as the ``globals``, and ``dict(vars(o))`` as the ``locals``,
144+
when calling :func:`eval`.
145+
* If ``o`` is a wrapped callable using :func:`functools.update_wrapper`,
146+
:func:`functools.wraps`, or :func:`functools.partial`, iteratively
147+
unwrap it by accessing either ``o.__wrapped__`` or ``o.func`` as
148+
appropriate, until you have found the root unwrapped function.
149+
* If ``o`` is a callable (but not a class), use
150+
``o.__globals__`` as the globals when calling :func:`eval`.
151+
152+
However, not all string values used as annotations can
153+
be successfully turned into Python values by :func:`eval`.
154+
String values could theoretically contain any valid string,
155+
and in practice there are valid use cases for type hints that
156+
require annotating with string values that specifically
157+
*can't* be evaluated. For example:
158+
159+
* :pep:`604` union types using `|`, before support for this
160+
was added to Python 3.10.
161+
* Definitions that aren't needed at runtime, only imported
162+
when :const:`typing.TYPE_CHECKING` is true.
163+
164+
If :func:`eval` attempts to evaluate such values, it will
165+
fail and raise an exception. So, when designing a library
166+
API that works with annotations, it's recommended to only
167+
attempt to evaluate string values when explicitly requested
168+
to by the caller.
169+
170+
171+
Best Practices For ``__annotations__`` In Any Python Version
172+
============================================================
173+
174+
* You should avoid assigning to the ``__annotations__`` member
175+
of objects directly. Let Python manage setting ``__annotations__``.
176+
177+
* If you do assign directly to the ``__annotations__`` member
178+
of an object, you should always set it to a ``dict`` object.
179+
180+
* If you directly access the ``__annotations__`` member
181+
of an object, you should ensure that it's a
182+
dictionary before attempting to examine its contents.
183+
184+
* You should avoid modifying ``__annotations__`` dicts.
185+
186+
* You should avoid deleting the ``__annotations__`` attribute
187+
of an object.
188+
189+
190+
``__annotations__`` Quirks
191+
==========================
192+
193+
In all versions of Python 3, function
194+
objects lazy-create an annotations dict if no annotations
195+
are defined on that object. You can delete the ``__annotations__``
196+
attribute using ``del fn.__annotations__``, but if you then
197+
access ``fn.__annotations__`` the object will create a new empty dict
198+
that it will store and return as its annotations. Deleting the
199+
annotations on a function before it has lazily created its annotations
200+
dict will throw an ``AttributeError``; using ``del fn.__annotations__``
201+
twice in a row is guaranteed to always throw an ``AttributeError``.
202+
203+
Everything in the above paragraph also applies to class and module
204+
objects in Python 3.10 and newer.
205+
206+
In all versions of Python 3, you can set ``__annotations__``
207+
on a function object to ``None``. However, subsequently
208+
accessing the annotations on that object using ``fn.__annotations__``
209+
will lazy-create an empty dictionary as per the first paragraph of
210+
this section. This is *not* true of modules and classes, in any Python
211+
version; those objects permit setting ``__annotations__`` to any
212+
Python value, and will retain whatever value is set.
213+
214+
If Python stringizes your annotations for you
215+
(using ``from __future__ import annotations``), and you
216+
specify a string as an annotation, the string will
217+
itself be quoted. In effect the annotation is quoted
218+
*twice.* For example::
219+
220+
from __future__ import annotations
221+
def foo(a: "str"): pass
222+
223+
print(foo.__annotations__)
224+
225+
This prints ``{'a': "'str'"}``. This shouldn't really be considered
226+
a "quirk"; it's mentioned here simply because it might be surprising.

Doc/howto/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ Currently, the HOWTOs are:
3030
ipaddress.rst
3131
clinic.rst
3232
instrumentation.rst
33+
annotations.rst
3334

Doc/library/inspect.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,9 @@ Classes and functions
11491149
with the result of calling :func:`eval()` on those values:
11501150

11511151
* If eval_str is true, :func:`eval()` is called on values of type ``str``.
1152+
(Note that ``get_annotations`` doesn't catch exceptions; if :func:`eval()`
1153+
raises an exception, it will unwind the stack past the ``get_annotations``
1154+
call.)
11521155
* If eval_str is false (the default), values of type ``str`` are unchanged.
11531156

11541157
``globals`` and ``locals`` are passed in to :func:`eval()`; see the documentation
@@ -1164,6 +1167,10 @@ Classes and functions
11641167
although if ``obj`` is a wrapped function (using
11651168
``functools.update_wrapper()``) it is first unwrapped.
11661169

1170+
Calling ``get_annotations`` is best practice for accessing the
1171+
annotations dict of any object. See :ref:`annotations-howto` for
1172+
more information on annotations best practices.
1173+
11671174
.. versionadded:: 3.10
11681175

11691176

Doc/reference/datamodel.rst

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,10 @@ Callable types
553553
| | the dict are the parameter | |
554554
| | names, and ``'return'`` for | |
555555
| | the return annotation, if | |
556-
| | provided. | |
556+
| | provided. For more | |
557+
| | information on working with | |
558+
| | this attribute, see | |
559+
| | :ref:`annotations-howto`. | |
557560
+-------------------------+-------------------------------+-----------+
558561
| :attr:`__kwdefaults__` | A dict containing defaults | Writable |
559562
| | for keyword-only parameters. | |
@@ -748,16 +751,29 @@ Modules
748751
single: __annotations__ (module attribute)
749752
pair: module; namespace
750753

751-
Predefined (writable) attributes: :attr:`__name__` is the module's name;
752-
:attr:`__doc__` is the module's documentation string, or ``None`` if
753-
unavailable; :attr:`__annotations__` (optional) is a dictionary containing
754-
:term:`variable annotations <variable annotation>` collected during module
755-
body execution; :attr:`__file__` is the pathname of the file from which the
756-
module was loaded, if it was loaded from a file. The :attr:`__file__`
757-
attribute may be missing for certain types of modules, such as C modules
758-
that are statically linked into the interpreter; for extension modules
759-
loaded dynamically from a shared library, it is the pathname of the shared
760-
library file.
754+
Predefined (writable) attributes:
755+
756+
:attr:`__name__`
757+
The module's name.
758+
759+
:attr:`__doc__`
760+
The module's documentation string, or ``None`` if
761+
unavailable.
762+
763+
:attr:`__file__`
764+
The pathname of the file from which the
765+
module was loaded, if it was loaded from a file.
766+
The :attr:`__file__`
767+
attribute may be missing for certain types of modules, such as C modules
768+
that are statically linked into the interpreter. For extension modules
769+
loaded dynamically from a shared library, it's the pathname of the shared
770+
library file.
771+
772+
:attr:`__annotations__`
773+
A dictionary containing
774+
:term:`variable annotations <variable annotation>` collected during
775+
module body execution. For best practices on working
776+
with :attr:`__annotations__`, please see :ref:`annotations-howto`.
761777

762778
.. index:: single: __dict__ (module attribute)
763779

@@ -821,14 +837,30 @@ Custom classes
821837
single: __doc__ (class attribute)
822838
single: __annotations__ (class attribute)
823839

824-
Special attributes: :attr:`~definition.__name__` is the class name; :attr:`__module__` is
825-
the module name in which the class was defined; :attr:`~object.__dict__` is the
826-
dictionary containing the class's namespace; :attr:`~class.__bases__` is a
827-
tuple containing the base classes, in the order of their occurrence in the
828-
base class list; :attr:`__doc__` is the class's documentation string,
829-
or ``None`` if undefined; :attr:`__annotations__` (optional) is a dictionary
830-
containing :term:`variable annotations <variable annotation>` collected during
831-
class body execution.
840+
Special attributes:
841+
842+
:attr:`~definition.__name__`
843+
The class name.
844+
845+
:attr:`__module__`
846+
The name of the module in which the class was defined.
847+
848+
:attr:`~object.__dict__`
849+
The dictionary containing the class's namespace.
850+
851+
:attr:`~class.__bases__`
852+
A tuple containing the base classes, in the order of
853+
their occurrence in the base class list.
854+
855+
:attr:`__doc__`
856+
The class's documentation string, or ``None`` if undefined.
857+
858+
:attr:`__annotations__`
859+
A dictionary containing
860+
:term:`variable annotations <variable annotation>`
861+
collected during class body execution. For best practices on
862+
working with :attr:`__annotations__`, please see
863+
:ref:`annotations-howto`.
832864

833865
Class instances
834866
.. index::

Doc/whatsnew/3.10.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,9 @@ Other Language Changes
807807
808808
* Class and module objects now lazy-create empty annotations dicts on demand.
809809
The annotations dicts are stored in the object’s ``__dict__`` for
810-
backwards compatibility.
810+
backwards compatibility. This improves the best practices for working
811+
with ``__annotations__``; for more information, please see
812+
:ref:`annotations-howto`.
811813
(Contributed by Larry Hastings in :issue:`43901`.)
812814
813815
New Modules
@@ -996,7 +998,9 @@ defined on an object. It works around the quirks of accessing the annotations
996998
on various types of objects, and makes very few assumptions about the object
997999
it examines. :func:`inspect.get_annotations` can also correctly un-stringize
9981000
stringized annotations. :func:`inspect.get_annotations` is now considered
999-
best practice for accessing the annotations dict defined on any Python object.
1001+
best practice for accessing the annotations dict defined on any Python object;
1002+
for more information on best practices for working with annotations, please see
1003+
:ref:`annotations-howto`.
10001004
Relatedly, :func:`inspect.signature`,
10011005
:func:`inspect.Signature.from_callable`, and ``inspect.Signature.from_function``
10021006
now call :func:`inspect.get_annotations` to retrieve annotations. This means
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add "Annotations Best Practices" document as a new HOWTO.

0 commit comments

Comments
 (0)
X Tutup