Pep Evaluation Essays

PEP 563 -- Postponed Evaluation of Annotations


Abstract

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 in string form.

This change is going to be introduced gradually, starting with a new 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.

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 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

In Python 4.0, function and variable annotations will no longer be evaluated at definition time. Instead, a string form will be preserved in the respective 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. ). 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 ).

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:

from __future__ import annotations

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 function correctly evaluates expressions back from its string form. Note that all valid code currently using 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 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 function automatically resolves the correct value of for functions and classes. It also automatically provides the correct for classes.

When running , the value of globals can be gathered in the following way:

  • function objects hold a reference to their respective globals in an attribute called ;

  • classes hold the name of the module they were defined in, this can be used to retrieve the respective globals:

    cls_globals = vars(sys.modules[SomeClass.__module__])

    Note that this needs to be repeated for base classes to evaluate all .

  • modules should use their own .

The value of cannot be reliably retrieved for functions because in all likelihood the stack frame at the time of the call no longer exists.

For classes, 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:

def class_decorator(cls): annotations = get_type_hints(cls) # raises NameError on 'C' print(f'Annotations for {cls}: {annotations}') return cls @class_decorator class C: singleton: 'C' = None

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

Sometimes there's code that must be seen by a type checker but should not be executed. For such situations the module defines a constant, , that is considered during type checking but at runtime. Example:

import typing if typing.TYPE_CHECKING: import expensive_mod def a_func(arg: expensive_mod.SomeClass) -> None: a_var: expensive_mod.SomeClass = arg ...

This approach is also useful when handling import cycles.

Trying to resolve annotations of at runtime using will fail since the name is not defined ( variable being 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 or .

Annotations that depend on locals at the time of the function definition will not be resolvable later. Example:

def generate(): A = Optional[int] class C: field: A = 1 def method(self, arg: A) -> None: ... return C X = generate()

Trying to resolve annotations of later by using will fail because 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:

class C: field = 'c_field' def method(self) -> C.field: # this is OK ... def method(self) -> field: # this is OK ... def method(self) -> C.D: # this is OK ... def method(self) -> D: # this is OK ... class D: field2 = 'd_field' def method(self) -> C.D.field2: # this is OK ... def method(self) -> D.field2: # this is OK ... def method(self) -> field2: # this is OK ... def method(self) -> field: # this FAILS, class D doesn't ... # see C's attributes, This was # already true before this PEP.

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 import is required to use the described functionality. No warnings are raised.

In Python 3.8 a is raised by the compiler in the presence of type annotations in modules without the import.

Starting with Python 3.9 the warning becomes a .

In Python 4.0 this will become the default behavior. Use of annotations incompatible with this PEP 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 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 module that use other syntactic constructs of the language, and those will still require working around forward references with string literals. The list includes:

  • type definitions:

    T = TypeVar('T', bound='<type>') UserId = NewType('UserId', '<type>') Employee = NamedTuple('Employee', [('name', '<type>'), ('id', '<type>')])
  • aliases:

    Alias = Optional['<type>'] AnotherAlias = Union['<type>', '<type>'] YetAnotherAlias = '<type>'
  • casting:

    cast('<type>', value)
  • base classes:

    class C(Tuple['<type>', '<type>']): ...

Depending on the specific case, some of the cases listed above might be worked around by placing the usage in a 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 , 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 .

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 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, 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:

  1. Add a new special attribute to functions: .
  2. Make a lazy dynamic mapping, evaluating expressions from the corresponding key in just-in-time.

This idea is supposed to solve the backwards compatibility issue, removing the need for a new 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 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 is an unnecessary step if 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

This PEP originally suggested directly storing the contents of a string literal under its respective key in . 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:

from __future__ import annotations

the PEP could call the feature more explicitly, for example , , , , , , etc.

The problem with those names is that they are very verbose. Each of them besides 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:

from __future__ import division

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:

A compromise is possible where a import could enable turning all annotations in a given module into string literals, as follows:

from __future__ import annotations class ImSet: def add(self, a: ImSet) -> List[ImSet]: ... assert ImSet.add.__annotations__ == { 'a': 'ImSet', 'return': 'List[ImSet]' }

Such a 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 . This tends to be confusing to beginners:

Why this:

from typing import List, Set def dir(o: object = ...) -> List[str]: ... def add_friends(friends: Set[Friend]) -> None: ...

But not this:

def dir(o: object = ...) -> list[str]: ... def add_friends(friends: set[Friend]) -> None ...

Why this:

up_to_ten = list(range(10)) friends = set()

But not this:

from typing import List, Set up_to_ten = List[int](range(10)) friends = Set[Friend]()

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 , just like this PEP specifies. This idea was received well. Ivan Levkivskyi supported using the import and suggested unparsing the AST in . 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:

One thing that comes to mind is that it's a very random change to the language. It might be useful to have a more compact way to indicate deferred execution of expressions (using less syntax than ). But why would the use case of type annotations be so all-important to change the language to do it there first (rather than proposing a more general solution), given that there's already a solution for this particular use case that requires very minimal syntax?

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 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 on the annotation with the correct and set.

This detail about and 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 (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 import. Many debaters agreed that seems like an overly broad name for the feature name. Guido van Rossum briefly decided to call it but then changed his mind, arguing that 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 verbatim.

A side-thread of discussion started around the runtime penalty of static typing, with topic like the import time of the module (which is comparable to without dependencies, and three times as heavy as 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 throroughly reviewed by Serhiy Storchaka who found all sorts of issues, including bugs, bad readability, and performance problems.

Copyright

This document has been placed in the public domain.

Source: https://github.com/python/peps/blob/master/pep-0563.rst

Essay/Term paper: Personal writing: the evaluation of my coaches

Essay, term paper, research paper:  College Essays

See all college papers and term papers on College Essays

Need a different (custom) essay on College Essays? Buy a custom essay on College Essays

Need a custom research paper on College Essays? Click here to buy a custom term paper.



Personal Writing: The Evaluation of My Coaches


Throughout my high school basketball career I experienced several
different coaches. Every new coach brought a different approach to coaching.
The varying techniques of coaching brought about different attitudes and
expectations during practices and games. I found that during practice, coaches
had either the nice-guy or the drill sergeant approach. They also had different
methods of coaching during and after the games.
Practices are very important to basketball. If you practice hard and
take it seriously, your team can become successful. All of my coaches in high
school took practices seriously. I basically had two different types of coaches
when it came to practice. There was the drill sergeant type, which had the team
line up in the same place every day to do our calisthenics before each practice.
The team captain stood facing the rest of the team and lead us in various
stretches and other warm-ups. The coach was very strict. No horse play or
unnecessary talking or anything else we knew would make the coach mad. Coach
would have us do drills having to do with the plays we ran during the game. If
we made a mistake coach would stop us and make an example of whoever messed up.
He would say, "Did everyone see what Bryan just did? That is what you should
not do." He would then gripe a little and after that we would continue our
practice. I feel that this method of coaching during practice made my teammates
and I closer as a team during the game, but we always dreaded practice. I
prefer the nice-guy coaching method during practice. This makes practice more
fun. The coach is serious about making our team better, but he realizes that
people are not perfect. During calisthenics he would talk to us about our day
at school and tell us dirty jokes, and just try to be our friend. While we were
practicing he pointed out our mistakes, but when we did something good he
praised us. This made practice fun and everyone was more at ease during
practices.
All of my coaches gave a pre-game and half time pep talk. One of the
coaches I had would tell us how the other team played, who we needed to keep
from shooting, and then tell us to go out there and kick some butt. This was
his basic pre-game pep talk. At half time, if we had been playing bad, this
coach would come in the locker room cussing. He had a pace-maker and when he
would get mad at us you could hear it going tick, tick, tick, tick rapidly. He
was a little on the crazy side. Other coaches, during pre-game pep talk, would
tell us a little story that would move us in such a way that made us want to
play hard and win. During half time if we played horrible every coach I had
would come in cussing. If we had been playing good some coaches would come in
say, "Good game." then leave. Others would come in and point out specific
things that we did good. The coaches that came in pointing out specific good
points about the game was my favorite.
The attitude of any one coach was different if we won the game or if we
lost the game. Let's take, for example if we won the game. No matter if the
game was close or not every coach I had would come in the locker room a
congratulate each player individually. Then they would address us as a whole
team and tell us we all did a great job. It was a different story if we lost
the game. If the other team was really good the coaches would come in and tell
us that we tried our best, but the other team had talent. If we lost because we
played bad some coaches would come in and yell at us. Others would say, "You
guys know what you did wrong and I don't feel like talking to ya'll right now."
The next day at practice we would go over the things we did wrong in the
previous game. The team did not like to lose.
I believe that it is good for a basketball player to experience all
different types of coaches. If I was a coach I would have the more laid back
approach. A basketball player plays the sport because he or she loves the game.
If a coach is always negative then the player will not be having fun. This
discourages the player and could possibly lead to them quitting. You do have to
correct players when they do something wrong or you would not be doing your job
as a coach, but it can be done in a nice way. I only had one coach that was out
there to have fun and that is the basketball season I enjoyed the most.


 

Other sample model essays:

College Essays / Eve And The Apple

Eve and the Apple No one completely understands the ways of God. Many of us can come up with our own opinions, and justify his ways in our own minds, just as Milton did in Paradise Lost. ...

Charles Dickens / A Character Sketch Of Joe Gargery

A Character Sketch of Joe Gargery Joe Gargery might not be the smartest or wisest of Dickens' characters, but he is definitely one of the kindest and most humane. Although Miss Havisham g...

College Essays / Native Son: Bigger

Native Son: Bigger Who can forget the fires blazing over local buildings during the Los Angeles Riots? Unfortunately the whole event does not seem as if it was too far off in the past. A...

English Composition / Humanity's Fall In "The Garden Of Eden"

Humanity's Fall In "The Garden of Eden" The original sin that led to humanity's fall in the Garden of Eden is by far the worst sin committed by humankind. It is this sin that led to...

English Composition / Exams Are Unfair Assessments Of Progress

Exams Are Unfair Assessments of Progress Most educators believe that exams are the best way to judge a student's ability. They believe that students' ability can be judged depending on the...

College Essays / Existentialism In The Early 19th Century

Existentialism in the Early 19th Century Major Themes Because of the diversity of positions associated with existentialism, the term is impossible to define precisely. Certain themes common...

College Essays / Existentialism

Existentialism In our individual routines, each and every one of us strive to be the best that we are capable of being. How peculiar this is; we aim for similar goals, yet the meth...

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *