mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +00:00
gh-107432 Fix incorrect indentation in annotations HOWTO (#107445)
gh-107432 Fix incorrect indentation in annotations document Body text in https://docs.python.org/3/howto/annotations.html was indented throughout, and was being rendered in blockquote elements.
This commit is contained in:
parent
85e5b1f5b8
commit
5e2746d6e2
1 changed files with 148 additions and 148 deletions
|
@ -32,201 +32,201 @@ Annotations Best Practices
|
|||
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
|
||||
and newer, 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.
|
||||
Python 3.10 adds a new function to the standard library:
|
||||
:func:`inspect.get_annotations`. In Python versions 3.10
|
||||
and newer, 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.
|
||||
|
||||
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.
|
||||
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)``.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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::
|
||||
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 Base:
|
||||
a: int = 3
|
||||
b: str = 'abc'
|
||||
|
||||
class Derived(Base):
|
||||
pass
|
||||
class Derived(Base):
|
||||
pass
|
||||
|
||||
print(Derived.__annotations__)
|
||||
print(Derived.__annotations__)
|
||||
|
||||
This will print the annotations dict from ``Base``, not
|
||||
``Derived``.
|
||||
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 ``__dict__`` dictionary. Since
|
||||
the class may or may not have annotations defined, best practice
|
||||
is to call the ``get`` method on the class dict.
|
||||
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 ``__dict__`` dictionary. Since
|
||||
the class may or may not have annotations defined, best practice
|
||||
is to call the ``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::
|
||||
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)
|
||||
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.
|
||||
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 ``__dict__`` attribute, so for extra safety you may also wish
|
||||
to use :func:`getattr` to access ``__dict__``.
|
||||
Note that some exotic or malformed type objects may not have
|
||||
a ``__dict__`` attribute, so for extra safety you may also wish
|
||||
to use :func:`getattr` to access ``__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.
|
||||
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.
|
||||
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``:
|
||||
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
|
||||
``o.__globals__`` as the globals when calling :func:`eval`.
|
||||
* 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
|
||||
``o.__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:
|
||||
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.
|
||||
* :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.
|
||||
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__``.
|
||||
* 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.
|
||||
* If you do assign directly to the ``__annotations__`` member
|
||||
of an object, you should always set it to a ``dict`` object.
|
||||
|
||||
* If you directly access the ``__annotations__`` member
|
||||
of an object, you should ensure that it's a
|
||||
dictionary before attempting to examine its contents.
|
||||
* If you 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 modifying ``__annotations__`` dicts.
|
||||
|
||||
* You should avoid deleting the ``__annotations__`` attribute
|
||||
of an object.
|
||||
* 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``.
|
||||
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.
|
||||
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.
|
||||
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::
|
||||
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
|
||||
from __future__ import annotations
|
||||
def foo(a: "str"): pass
|
||||
|
||||
print(foo.__annotations__)
|
||||
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.
|
||||
This prints ``{'a': "'str'"}``. This shouldn't really be considered
|
||||
a "quirk"; it's mentioned here simply because it might be surprising.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue