What is new in Python 3.9?
Here we will see some of the interesting features of Python 3.9
![]()
Python is an interpreted, high-level and general-purpose programming language. Created by Guido van Rossum and first released in 1991, Python’s design philosophy emphasizes code readability with its notable use of significant whitespace.
Recently Python has launched its latest version Python 3.9 with some of the most awaited changes which will definitely support its all users.
PEP 584 — Add Union Operators To dict
This PEP proposes adding merge (|) and update (|=) operators to the built-in dict class.
After this PEP was accepted, the decision was made to also implement the new operators for several other standard library mappings.
The current ways to merge two dicts have several disadvantages:
dict.update
d1.update(d2) modifies d1 in-place. e = d1.copy(); e.update(d2) is not an expression and needs a temporary variable.
Dict unpacking looks ugly and is not easily discoverable. Few people would be able to guess what it means the first time they see it, or think of it as the “obvious way” to merge two dicts.
<**d1, **d2>ignores the types of the mappings and always returns a dict. type(d1)(<**d1, **d2>) fails for dict subclasses such as defaultdict that have an incompatible __init__ method.
PEP 585 — Type Hinting Generics In Standard Collections
Static typing as defined by PEPs 484, 526, 544, 560, and 563 was built incrementally on top of the existing Python runtime and constrained by existing syntax and runtime behavior. This led to the existence of a duplicated collection hierarchy in the typing module due to generics (for example typing.List and the built-in list).
This PEP proposes to enable support for the generics syntax in all standard collections currently available in the typing module.
This change removes the necessity for a parallel type hierarchy in the typing module, making it easier for users to annotate their programs and easier for teachers to teach Python.
Generic (n.) — a type that can be parameterized, typically a container. Also known as a parametric type or a generic type. For example: dict.
parameterized generic — a specific instance of a generic with the expected types for container elements provided. Also known as a parameterized type. For example: dict[str, int].
Tooling, including type checkers and linters, will have to be adapted to recognize standard collections as generics.
On the source level, the newly described functionality requires Python 3.9. For use cases restricted to type annotations, Python files with the “annotations” future-import (available since Python 3.7) can parameterize standard collections, including builtins. To reiterate, that depends on the external tools understanding that this is valid.
Starting with Python 3.7, when from __future__ import annotations is used, function and variable annotations can parameterize standard collections directly. Example:
Usefulness of this syntax before PEP 585 is limited as external tooling like Mypy does not recognize standard collections as generic. Moreover, certain features of typing like type aliases or casting require putting types outside of annotations, in runtime context. While these are relatively less common than type annotations, it’s important to allow using the same type syntax in all contexts.
PEP 614 — Relaxing Grammar Restrictions On Decorators
Python currently requires that all decorators consist of a dotted name, optionally followed by a single call. This PEP proposes removing these limitations and allowing decorators to be any valid expression. When decorators were first being introduced, Guido described the motivation to limit their syntax as a preference, not a technical requirement.
While these limitations were rarely encountered in practice, BPO issues and mailing list posts have consistently surfaced over the years requesting that they be removed. The most recent one (which prompted this proposal) contained a good example of code using the PyQt5 library that would become more readable, idiomatic, and maintainable if the existing restrictions were relaxed. Slightly modified:
Currently, these decorations must be rewritten as something like:
Further, the current grammar is already loose enough that it’s trivial to hack more complicated decorator expressions together. So rather than disallow arbitrarily complex expressions, as intended, the current restrictions only make them uglier and less efficient:
PEP 616 — String methods to remove prefixes and suffixes
This is a proposal to add two new methods, removeprefix() and removesuffix(), to the APIs of Python’s various string objects. These methods would remove a prefix or suffix (respectively) from a string, if present, and would be added to Unicode str objects, binary bytes and bytearray objects, and collections.UserString.
Rationale
There have been repeated issues on Python-Ideas [2] [3], Python-Dev [4] [5] [6] [7], the Bug Tracker, and StackOverflow [8], related to user confusion about the existing str.lstrip and str.rstrip methods. These users are typically expecting the behavior of removeprefix and removesuffix, but they are surprised that the parameter for lstrip is interpreted as a set of characters, not a substring. This repeated issue is evidence that these methods are useful. The new methods allow a cleaner redirection of users to the desired behavior.
As another testimonial for the usefulness of these methods, several users on Python-Ideas [2] reported frequently including similar functions in their code for productivity. The implementation often contained subtle mistakes regarding the handling of the empty string, so a well-tested built-in method would be useful.
The existing solutions for creating the desired behavior are to either implement the methods as in the Specification below, or to use regular expressions as in the expression re.sub(‘^’ + re.escape(prefix), ‘’, s), which is less discoverable, requires a module import, and results in less readable code.
Specification
The builtin str class will gain two new methods which will behave as follows when type(self) is type(prefix) is type(suffix) is str:
When the arguments are instances of str subclasses, the methods should behave as though those arguments were first coerced to base str objects, and the return value should always be a base str.
Methods with the corresponding semantics will be added to the builtin bytes and bytearray objects. If b is either a bytes or bytearray object, then b.removeprefix() and b.removesuffix() will accept any bytes-like object as an argument. The two methods will also be added to collections.UserString, with similar behavior.
Motivating examples from the Python standard library
The examples below demonstrate how the proposed methods can make code one or more of the following:
- Less fragile:
- The code will not depend on the user to count the length of a literal.
- More performant:
- The code does not require a call to the Python built-in len function nor to the more expensive str.replace() method.
- More descriptive:
- The methods give a higher-level API for code readability as opposed to the traditional method of string slicing.
find_recursionlimit.py
- Current:
- Improved:
deccheck.py
This is an interesting case because the author chose to use the str.replace method in a situation where only a prefix was intended to be removed.
- Current:
- Improved:
- Arguably further improved:
cookiejar.py
- Current:
def strip_quotes(text): if text.startswith(‘“‘): text = text[1:] if text.endswith(‘“‘): text = text[:-1] return text
- Improved:
test_concurrent_futures.py
In the following example, the meaning of the code changes slightly, but in context, it behaves the same.
- Current:
- Improved:
PEP 593 — Flexible function and variable annotations
This PEP introduces a mechanism to extend the type annotations from PEP 484 with arbitrary metadata.PEP 484 provides a standard semantic for the annotations introduced in PEP 3107. PEP 484 is prescriptive but it is the de-facto standard for most of the consumers of annotations; in many statically checked code bases, where type annotations are widely used, they have effectively crowded out any other form of annotation. Some of the use cases for annotations described in PEP 3107 (database mapping, foreign languages bridge) are not currently realistic given the prevalence of type annotations. Furthermore the standardisation of type annotations rules out advanced features only supported by specific type checkers.
This PEP adds an Annotated type to the typing module to decorate existing types with context-specific metadata. Specifically, a type T can be annotated with metadata x via the typehint Annotated[T, x]. This metadata can be used for either static analysis or at runtime. If a library (or tool) encounters a typehint Annotated[T, x] and has no special logic for metadata x, it should ignore it and simply treat the type as T. Unlike the no_type_check functionality that currently exists in the typing module which completely disables typechecking annotations on a function or a class, the Annotated type allows for both static typechecking of T (e.g., via mypy [mypy] or Pyre [pyre], which can safely ignore x) together with runtime access to x within a specific application. The introduction of this type would address a diverse set of use cases of interest to the broader Python community.
This was originally brought up as issue 600 [issue-600] in the typing github and then discussed in Python ideas [python-ideas].
Combining runtime and static uses of annotations
There’s an emerging trend of libraries leveraging the typing annotations at runtime (e.g.: dataclasses); having the ability to extend the typing annotations with external data would be a great boon for those libraries.
Here’s an example of how a hypothetical module could leverage annotations to read c structs:
Lowering barriers to developing new typing constructs
Typically when adding a new type, a developer need to upstream that type to the typing module and change mypy, PyCharm [pycharm], Pyre, pytype [pytype], etc… This is particularly important when working on open-source code that makes use of these types, seeing as the code would not be immediately transportable to other developers’ tools without additional logic. As a result, there is a high cost to developing and trying out new types in a codebase. Ideally, authors should be able to introduce new types in a manner that allows for graceful degradation (e.g.: when clients do not have a custom mypy plugin [mypy-plugin]), which would lower the barrier to development and ensure some degree of backward compatibility.
For example, suppose that an author wanted to add support for tagged unions [tagged-union] to Python. One way to accomplish would be to annotate TypedDict [typed-dict] in Python such that only one field is allowed to be set:
This is a somewhat cumbersome syntax but it allows us to iterate on this proof-of-concept and have people with type checkers (or other tools) that don’t yet support this feature work in a codebase with tagged unions. The author could easily test this proposal and iron out the kinks before trying to upstream tagged union to typing, mypy, etc. Moreover, tools that do not have support for parsing the TaggedUnion annotation would still be able able to treat Currency as a TypedDict, which is still a close approximation (slightly less strict).
Specification
Syntax
Annotated is parameterized with a type and an arbitrary list of Python values that represent the annotations. Here are the specific details of the syntax:
- The first argument to Annotated must be a valid type
- Multiple type annotations are supported (Annotated supports variadic arguments):
Annotated[int, ValueRange(3, 10), ctype(“char”)]
Annotated must be called with at least two arguments ( Annotated[int] is not valid)
The order of the annotations is preserved and matters for equality checks:
Annotated[int, ValueRange(3, 10), ctype(“char”)] != Annotated[ int, ctype(“char”), ValueRange(3, 10) ]
- Nested Annotated types are flattened, with metadata ordered starting with the innermost annotation:
- Annotated[Annotated[int, ValueRange(3, 10)], ctype(“char”)] == Annotated[ int, ValueRange(3, 10), ctype(“char”) ]
Duplicated annotations are not removed:
- Annotated[int, ValueRange(3, 10)] != Annotated[ int, ValueRange(3, 10), ValueRange(3, 10) ]
Annotated can be used with nested and generic aliases:
Typevar T = … Vec = Annotated[List[Tuple[T, T]], MaxLen(10)] V = Vec[int] V == Annotated[List[Tuple[int, int]], MaxLen(10)]
Consuming annotations
Ultimately, the responsibility of how to interpret the annotations (if at all) is the responsibility of the tool or library encountering the Annotated type. A tool or library encountering an Annotated type can scan through the annotations to determine if they are of interest (e.g., using isinstance()).
Unknown annotations: When a tool or a library does not support annotations or encounters an unknown annotation it should just ignore it and treat annotated type as the underlying type. For example, when encountering an annotation that is not an instance of struct2.ctype to the annotations for name (e.g., Annotated[str, ‘foo’, struct2.ctype(“<10s”)]), the unpack method should ignore it.
Namespacing annotations: Namespaces are not needed for annotations since the class used by the annotations acts as a namespace.
Multiple annotations: It’s up to the tool consuming the annotations to decide whether the client is allowed to have several annotations on one type and how to merge those annotations.
Since the Annotated type allows you to put several annotations of the same (or different) type(s) on any node, the tools or libraries consuming those annotations are in charge of dealing with potential duplicates. For example, if you are doing value range analysis you might allow this:
Flattening nested annotations, this translates to:
Interaction with get_type_hints()
typing.get_type_hints() will take a new argument include_extras that defaults to False to preserve backward compatibility. When include_extras is False, the extra annotations will be stripped out of the returned value. Otherwise, the annotations will be returned unchanged:
Aliases & Concerns over verbosity
Writing typing.Annotated everywhere can be quite verbose; fortunately, the ability to alias annotations means that in practice we don’t expect clients to have to write lots of boilerplate code:
End Notes
The point of this article was to gather & demonstrate the concept of some of the important new changes with the announcement of Python 3.9. The last but not the least we can still run Python codes of previous version 3.x with 3.9 release. I have tried to identify some of the major new features after the announcement of Python 3.9.
PEP 563 – Postponed Evaluation of Annotations
PEP 3107 introduced syntax for function annotations, but the semantics were deliberately left undefined. PEP 484 introduced a standard meaning to annotations: type hints. PEP 526 defined variable annotations, explicitly tying them with the type hinting use case.
This PEP proposes changing function annotations and variable annotations so that they are no longer evaluated at function definition time. Instead, they are preserved in __annotations__ in string form.
This change is being introduced gradually, starting with a __future__ import in Python 3.7.
Rationale and Goals
PEP 3107 added support for arbitrary annotations on parts of a function definition. Just like default values, annotations are evaluated at function definition time. This creates a number of issues for the type hinting use case:
- forward references: when a type hint contains names that have not been defined yet, that definition needs to be expressed as a string literal;
- type hints are executed at module import time, which is not computationally free.
Postponing the evaluation of annotations solves both problems. NOTE: PEP 649 proposes an alternative solution to the above issues, putting this PEP in danger of being superseded.
Non-goals
Just like in PEP 484 and PEP 526, it should be emphasized that Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.
This PEP is meant to solve the problem of forward references in type annotations. There are still cases outside of annotations where forward references will require usage of string literals. Those are listed in a later section of this document.
Annotations without forced evaluation enable opportunities to improve the syntax of type hints. This idea will require its own separate PEP and is not discussed further in this document.
Non-typing usage of annotations
While annotations are still available for arbitrary use besides type checking, it is worth mentioning that the design of this PEP, as well as its precursors (PEP 484 and PEP 526), is predominantly motivated by the type hinting use case.
In Python 3.8 PEP 484 will graduate from provisional status. Other enhancements to the Python programming language like PEP 544, PEP 557, or PEP 560, are already being built on this basis as they depend on type annotations and the typing module as defined by PEP 484. In fact, the reason PEP 484 is staying provisional in Python 3.7 is to enable rapid evolution for another release cycle that some of the aforementioned enhancements require.
With this in mind, uses for annotations incompatible with the aforementioned PEPs should be considered deprecated.
Implementation
With this PEP, function and variable annotations will no longer be evaluated at definition time. Instead, a string form will be preserved in the respective __annotations__ dictionary. Static type checkers will see no difference in behavior, whereas tools using annotations at runtime will have to perform postponed evaluation.
The string form is obtained from the AST during the compilation step, which means that the string form might not preserve the exact formatting of the source. Note: if an annotation was a string literal already, it will still be wrapped in a string.
Annotations need to be syntactically valid Python expressions, also when passed as literal strings (i.e. compile(literal, », ‘eval’) ). Annotations can only use names present in the module scope as postponed evaluation using local names is not reliable (with the sole exception of class-level names resolved by typing.get_type_hints() ).
Note that as per PEP 526, local variable annotations are not evaluated at all since they are not accessible outside of the function’s closure.
Enabling the future behavior in Python 3.7
The functionality described above can be enabled starting from Python 3.7 using the following special import:
A reference implementation of this functionality is available on GitHub.
Resolving Type Hints at Runtime
To resolve an annotation at runtime from its string form to the result of the enclosed expression, user code needs to evaluate the string.
For code that uses type hints, the typing.get_type_hints(obj, globalns=None, localns=None) function correctly evaluates expressions back from its string form. Note that all valid code currently using __annotations__ should already be doing that since a type annotation can be expressed as a string literal.
For code which uses annotations for other purposes, a regular eval(ann, globals, locals) call is enough to resolve the annotation.
In both cases it’s important to consider how globals and locals affect the postponed evaluation. An annotation is no longer evaluated at the time of definition and, more importantly, in the same scope where it was defined. Consequently, using local state in annotations is no longer possible in general. As for globals, the module where the annotation was defined is the correct context for postponed evaluation.
The get_type_hints() function automatically resolves the correct value of globalns for functions and classes. It also automatically provides the correct localns for classes.
When running eval() , the value of globals can be gathered in the following way:
- function objects hold a reference to their respective globals in an attribute called __globals__ ;
- classes hold the name of the module they were defined in, this can be used to retrieve the respective globals:
Note that this needs to be repeated for base classes to evaluate all __annotations__ .
The value of localns cannot be reliably retrieved for functions because in all likelihood the stack frame at the time of the call no longer exists.
For classes, localns can be composed by chaining vars of the given class and its base classes (in the method resolution order). Since slots can only be filled after the class was defined, we don’t need to consult them for this purpose.
Runtime annotation resolution and class decorators
Metaclasses and class decorators that need to resolve annotations for the current class will fail for annotations that use the name of the current class. Example:
This was already true before this PEP. The class decorator acts on the class before it’s assigned a name in the current definition scope.
Runtime annotation resolution and TYPE_CHECKING
Sometimes there’s code that must be seen by a type checker but should not be executed. For such situations the typing module defines a constant, TYPE_CHECKING , that is considered True during type checking but False at runtime. Example:
This approach is also useful when handling import cycles.
Trying to resolve annotations of a_func at runtime using typing.get_type_hints() will fail since the name expensive_mod is not defined ( TYPE_CHECKING variable being False at runtime). This was already true before this PEP.
Backwards Compatibility
This is a backwards incompatible change. Applications depending on arbitrary objects to be directly present in annotations will break if they are not using typing.get_type_hints() or eval() .
Annotations that depend on locals at the time of the function definition will not be resolvable later. Example:
Trying to resolve annotations of X later by using get_type_hints(X) will fail because A and its enclosing scope no longer exists. Python will make no attempt to disallow such annotations since they can often still be successfully statically analyzed, which is the predominant use case for annotations.
Annotations using nested classes and their respective state are still valid. They can use local names or the fully qualified name. Example:
In the presence of an annotation that isn’t a syntactically valid expression, SyntaxError is raised at compile time. However, since names aren’t resolved at that time, no attempt is made to validate whether used names are correct or not.
Deprecation policy
Starting with Python 3.7, a __future__ import is required to use the described functionality. No warnings are raised.
NOTE: Whether this will eventually become the default behavior is currently unclear pending decision on PEP 649. In any case, use of annotations that depend upon their eager evaluation is incompatible with both proposals and is no longer supported.
Forward References
Deliberately using a name before it was defined in the module is called a forward reference. For the purpose of this section, we’ll call any name imported or defined within a if TYPE_CHECKING: block a forward reference, too.
This PEP addresses the issue of forward references in type annotations. The use of string literals will no longer be required in this case. However, there are APIs in the typing module that use other syntactic constructs of the language, and those will still require working around forward references with string literals. The list includes:
Depending on the specific case, some of the cases listed above might be worked around by placing the usage in a if TYPE_CHECKING: block. This will not work for any code that needs to be available at runtime, notably for base classes and casting. For named tuples, using the new class definition syntax introduced in Python 3.6 solves the issue.
In general, fixing the issue for all forward references requires changing how module instantiation is performed in Python, from the current single-pass top-down model. This would be a major change in the language and is out of scope for this PEP.
Rejected Ideas
Keeping the ability to use function local state when defining annotations
With postponed evaluation, this would require keeping a reference to the frame in which an annotation got created. This could be achieved for example by storing all annotations as lambdas instead of strings.
This would be prohibitively expensive for highly annotated code as the frames would keep all their objects alive. That includes predominantly objects that won’t ever be accessed again.
To be able to address class-level scope, the lambda approach would require a new kind of cell in the interpreter. This would proliferate the number of types that can appear in __annotations__ , as well as wouldn’t be as introspectable as strings.
Note that in the case of nested classes, the functionality to get the effective “globals” and “locals” at definition time is provided by typing.get_type_hints() .
If a function generates a class or a function with annotations that have to use local variables, it can populate the given generated object’s __annotations__ dictionary directly, without relying on the compiler.
Disallowing local state usage for classes, too
This PEP originally proposed limiting names within annotations to only allow names from the model-level scope, including for classes. The author argued this makes name resolution unambiguous, including in cases of conflicts between local names and module-level names.
This idea was ultimately rejected in case of classes. Instead, typing.get_type_hints() got modified to populate the local namespace correctly if class-level annotations are needed.
The reasons for rejecting the idea were that it goes against the intuition of how scoping works in Python, and would break enough existing type annotations to make the transition cumbersome. Finally, local scope access is required for class decorators to be able to evaluate type annotations. This is because class decorators are applied before the class receives its name in the outer scope.
Introducing a new dictionary for the string literal form instead
Yury Selivanov shared the following idea:
- Add a new special attribute to functions: __annotations_text__ .
- Make __annotations__ a lazy dynamic mapping, evaluating expressions from the corresponding key in __annotations_text__ just-in-time.
This idea is supposed to solve the backwards compatibility issue, removing the need for a new __future__ import. Sadly, this is not enough. Postponed evaluation changes which state the annotation has access to. While postponed evaluation fixes the forward reference problem, it also makes it impossible to access function-level locals anymore. This alone is a source of backwards incompatibility which justifies a deprecation period.
A __future__ import is an obvious and explicit indicator of opting in for the new functionality. It also makes it trivial for external tools to recognize the difference between a Python files using the old or the new approach. In the former case, that tool would recognize that local state access is allowed, whereas in the latter case it would recognize that forward references are allowed.
Finally, just-in-time evaluation in __annotations__ is an unnecessary step if get_type_hints() is used later.
Dropping annotations with -O
There are two reasons this is not satisfying for the purpose of this PEP.
First, this only addresses runtime cost, not forward references, those still cannot be safely used in source code. A library maintainer would never be able to use forward references since that would force the library users to use this new hypothetical -O switch.
Second, this throws the baby out with the bath water. Now no runtime annotation use can be performed. PEP 557 is one example of a recent development where evaluating type annotations at runtime is useful.
All that being said, a granular -O option to drop annotations is a possibility in the future, as it’s conceptually compatible with existing -O behavior (dropping docstrings and assert statements). This PEP does not invalidate the idea.
Passing string literals in annotations verbatim to __annotations__
This PEP originally suggested directly storing the contents of a string literal under its respective key in __annotations__ . This was meant to simplify support for runtime type checkers.
Mark Shannon pointed out this idea was flawed since it wasn’t handling situations where strings are only part of a type annotation.
The inconsistency of it was always apparent but given that it doesn’t fully prevent cases of double-wrapping strings anyway, it is not worth it.
Making the name of the future import more verbose
Instead of requiring the following import:
the PEP could call the feature more explicitly, for example string_annotations , stringify_annotations , annotation_strings , annotations_as_strings , lazy_annotations , static_annotations , etc.
The problem with those names is that they are very verbose. Each of them besides lazy_annotations would constitute the longest future feature name in Python. They are long to type and harder to remember than the single-word form.
There is precedence of a future import name that sounds overly generic but in practice was obvious to users as to what it does:
Prior discussion
In PEP 484
The forward reference problem was discussed when PEP 484 was originally drafted, leading to the following statement in the document:
Such a __future__ import statement may be proposed in a separate PEP.
python/typing#400
The problem was discussed at length on the typing module’s GitHub project, under Issue 400. The problem statement there includes critique of generic types requiring imports from typing . This tends to be confusing to beginners:
While typing usability is an interesting problem, it is out of scope of this PEP. Specifically, any extensions of the typing syntax standardized in PEP 484 will require their own respective PEPs and approval.
Issue 400 ultimately suggests postponing evaluation of annotations and keeping them as strings in __annotations__ , just like this PEP specifies. This idea was received well. Ivan Levkivskyi supported using the __future__ import and suggested unparsing the AST in compile.c . Jukka Lehtosalo pointed out that there are some cases of forward references where types are used outside of annotations and postponed evaluation will not help those. For those cases using the string literal notation would still be required. Those cases are discussed briefly in the “Forward References” section of this PEP.
The biggest controversy on the issue was Guido van Rossum’s concern that untokenizing annotation expressions back to their string form has no precedent in the Python programming language and feels like a hacky workaround. He said:
Eventually, Ethan Smith and schollii voiced that feedback gathered during PyCon US suggests that the state of forward references needs fixing. Guido van Rossum suggested coming back to the __future__ idea, pointing out that to prevent abuse, it’s important for the annotations to be kept both syntactically valid and evaluating correctly at runtime.
First draft discussion on python-ideas
Discussion happened largely in two threads, the original announcement and a follow-up called PEP 563 and expensive backwards compatibility.
The PEP received rather warm feedback (4 strongly in favor, 2 in favor with concerns, 2 against). The biggest voice of concern on the former thread being Steven D’Aprano’s review stating that the problem definition of the PEP doesn’t justify breaking backwards compatibility. In this response Steven seemed mostly concerned about Python no longer supporting evaluation of annotations that depended on local function/class state.
A few people voiced concerns that there are libraries using annotations for non-typing purposes. However, none of the named libraries would be invalidated by this PEP. They do require adapting to the new requirement to call eval() on the annotation with the correct globals and locals set.
This detail about globals and locals having to be correct was picked up by a number of commenters. Nick Coghlan benchmarked turning annotations into lambdas instead of strings, sadly this proved to be much slower at runtime than the current situation.
The latter thread was started by Jim J. Jewett who stressed that the ability to properly evaluate annotations is an important requirement and backwards compatibility in that regard is valuable. After some discussion he admitted that side effects in annotations are a code smell and modal support to either perform or not perform evaluation is a messy solution. His biggest concern remained loss of functionality stemming from the evaluation restrictions on global and local scope.
Nick Coghlan pointed out that some of those evaluation restrictions from the PEP could be lifted by a clever implementation of an evaluation helper, which could solve self-referencing classes even in the form of a class decorator. He suggested the PEP should provide this helper function in the standard library.
Second draft discussion on python-dev
Discussion happened mainly in the announcement thread, followed by a brief discussion under Mark Shannon’s post.
Steven D’Aprano was concerned whether it’s acceptable for typos to be allowed in annotations after the change proposed by the PEP. Brett Cannon responded that type checkers and other static analyzers (like linters or programming text editors) will catch this type of error. Jukka Lehtosalo added that this situation is analogous to how names in function bodies are not resolved until the function is called.
A major topic of discussion was Nick Coghlan’s suggestion to store annotations in “thunk form”, in other words as a specialized lambda which would be able to access class-level scope (and allow for scope customization at call time). He presented a possible design for it (indirect attribute cells). This was later seen as equivalent to “special forms” in Lisp. Guido van Rossum expressed worry that this sort of feature cannot be safely implemented in twelve weeks (i.e. in time before the Python 3.7 beta freeze).
After a while it became clear that the point of division between supporters of the string form vs. supporters of the thunk form is actually about whether annotations should be perceived as a general syntactic element vs. something tied to the type checking use case.
Finally, Guido van Rossum declared he’s rejecting the thunk idea based on the fact that it would require a new building block in the interpreter. This block would be exposed in annotations, multiplying possible types of values stored in __annotations__ (arbitrary objects, strings, and now thunks). Moreover, thunks aren’t as introspectable as strings. Most importantly, Guido van Rossum explicitly stated interest in gradually restricting the use of annotations to static typing (with an optional runtime component).
Nick Coghlan got convinced to PEP 563, too, promptly beginning the mandatory bike shedding session on the name of the __future__ import. Many debaters agreed that annotations seems like an overly broad name for the feature name. Guido van Rossum briefly decided to call it string_annotations but then changed his mind, arguing that division is a precedent of a broad name with a clear meaning.
The final improvement to the PEP suggested in the discussion by Mark Shannon was the rejection of the temptation to pass string literals through to __annotations__ verbatim.
A side-thread of discussion started around the runtime penalty of static typing, with topic like the import time of the typing module (which is comparable to re without dependencies, and three times as heavy as re when counting dependencies).
Acknowledgements
This document could not be completed without valuable input, encouragement and advice from Guido van Rossum, Jukka Lehtosalo, and Ivan Levkivskyi.
The implementation was thoroughly reviewed by Serhiy Storchaka who found all sorts of issues, including bugs, bad readability, and performance problems.
What is the benefit of "from __future__ import annotations" if classes are imported for type hints anyway?
What is the benefit of importing from __future__ import annotations ? When I understand it right I should stop unnecessary typing import in runtime.
In my example HelloWorld is only needed for typing. But with this code the output always is:
Should this happen? x = World!
When I remove from hello import HelloWorld the typing help in PyCharm does not longer work (I can understand this, because it does not understand where HelloWorld is from).
So my question is if I still need to do from hello import HelloWorld what benefits do I get from from __future__ import annotations ?
![]()
1 Answer 1
The from __future__ import annotations import has one core advantage: it makes using forward references cleaner.
For example, consider this (currently broken) program.
This program will actually crash when you try running it at runtime: you’ve tried using ‘MyClass’ before it’s actually ever defined. In order to fix this before, you had to either use the type-comment syntax or wrap each ‘MyClass’ in a string to create a forward reference:
Although this works, it feels very janky. Types should be types: we shouldn’t need to have to manually convert certain types into strings just to make types play nicely with the Python runtime.
We can fix this by including the from __future__ import annotations import: that line automatically makes all types a string at runtime. This lets us write code that looks like the first example without it crashing: since each type hint is actually a string at runtime, we’re no longer referencing something that doesn’t exist yet.
And typecheckers like mypy or Pycharm won’t care: to them, the type looks the same no matter how Python itself chooses to represent it.
One thing to note is that this import does not, by itself, let us avoid importing things. It simply makes it cleaner when doing so.
For example, consider the following:
If expensive_to_import_module does a lot of startup logic, that might mean it takes a non-negligible amount of time to import MyType . This won’t really make a difference once the program is actually running, but it does make the time-to-start slower. This can feel particularly bad if you’re trying to write short-lived command-line style programs: the act of adding type hints can sometimes make your program feel more sluggish when starting up.
We could fix this by making MyType a string while hiding the import behind an if TYPE_CHECKING guard, like so:
This works, but again looks clunky. Why should we need to add quotes around the last type? The annotations future import makes the syntax for doing this a little smoother:
Depending on your point-of-view, this may not seem like a huge win. But it does help make using type hints much more ergonomic, makes them feel more «integrated» into Python, and (once these become enabled by default) removes a common stumbling block newcomers to PEP 484 tend to trip over.
__future__ в Python

future был введен в Python 2.1, и его инструкции меняют способ интерпретации кода Python. Он сообщает интерпретатору скомпилировать некоторые операторы как те, которые будут доступны в будущих версиях Python, т.е. Python использует функцию from future import feature для резервного копирования функций из более высоких версий Python в текущий интерпретатор.
Когда вы видите from __future__ import , это означает, что функция из последней или предстоящей версии Python была перенесена в более раннюю версию.
В этом руководстве будут обсуждаться функции, уже включенные в Python 3 в более ранних версиях Python с использованием __future__ .
Используйте __future__ для print_function в Python
Использование ключевого слова print в качестве функции вместо оператора придает ему большую гибкость, что помогает расширить возможности ключевого слова print . Основная функция from __future__ import print_function — перенести функцию print из Python 3 в Python 2.
Обратите внимание, что print здесь используется как функция, которая ранее использовалась как оператор в Python 2.x.
Используйте __future__ для unicode_laterals в Python
Это позволяет вам изменять тип строковых литералов.
Строковые литералы в Python 2 по умолчанию являются ‘str’, но если мы используем from __future__ import unicode_literals , тип строкового литерала изменится на Unicode .
Но с from __future__ import unicode_literals мы получаем следующий вывод.
Обратите внимание, что при использовании from __future__ import нам не нужно добавлять к каждой строке префикс u , чтобы рассматривать ее как Unicode .
Используйте __future__ для разделения в Python
В версиях Python 2.x используется классическое разделение.
Простое деление 8 на 7 возвращает 0 в Python 2.x.
Использование from __future__ import division позволяет программе Python 2 использовать __truediv__() .
Используйте __future__ для absolute_import в Python
В Python 2 вы могли иметь только неявный относительный импорт, тогда как в Python 3 вы могли иметь явный или абсолютный импорт. __future__ import absolute_import позволяет заключать в скобки несколько операторов импорта, заключенных в скобки.
Обратите внимание, что без функции __future__ import absolute_import вы не сможете заключить несколько операторов import в одну строку кода.
Используйте __future__ для annotations в Python
Аннотации — это выражения Python, связанные с различными частями функции.
Здесь использование from __future__ import annotations меняет способ оценки типа аннотаций в модуле Python. Он откладывает оценку аннотаций и волшебным образом обрабатывает все аннотации как разные аннотации.
Обратите внимание, что приведенный выше код будет работать только в том случае, если __future__ import написано в верхней части кода, поскольку он изменяет то, как Python интерпретирует код, т.е. он обрабатывает аннотации как отдельные строки.
Используйте __future__ для вложенных областей
С добавлением __future__ import nested_scopes в Python были введены статически вложенные области видимости. Он позволяет запускать следующие типы кода без возврата ошибки.
Обратите внимание, что приведенный выше код вызвал бы ошибку NameError до Python 2.1.
Используйте __future__ для генераторов Python
Генератор — это функция в Python, определенная как обычная функция. Тем не менее, он делает это с ключевым словом yield, а не return всякий раз, когда ему нужно сгенерировать определенное значение.
Здесь с использованием from __future__ import generators были введены функции генератора для сохранения состояний между последовательными вызовами функций.
Используйте __future__ для утверждения with
Это исключает использование операторов try. except путем добавления оператора with в качестве ключевого слова в Python. Он в основном используется при выполнении файлового ввода-вывода, как показано в примере ниже.