Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 526 – Syntax for Variable Annotations

Author:
Ryan Gonzalez <rymg19 at gmail.com>, Philip House <phouse512 at gmail.com>, Ivan Levkivskyi <levkivskyi at gmail.com>, Lisa Roach <lisaroach14 at gmail.com>, Guido van Rossum <guido at python.org>
Status:
Final
Type:
Standards Track
Topic:
Typing
Created:
09-Aug-2016
Python-Version:
3.6
Post-History:
30-Aug-2016, 02-Sep-2016
Resolution:
Python-Dev message

Table of Contents

Important

This PEP is a historical document: seeAnnotated assignment statements,ClassVar andtyping.ClassVar for up-to-date specs and documentation. Canonical typing specs are maintained at thetyping specs site; runtime typing behaviour is described in the CPython documentation.

×

See thetyping specification update process for how to propose changes to the typing spec.

Status

This PEP has been provisionally accepted by the BDFL.See the acceptance message for more color:https://mail.python.org/pipermail/python-dev/2016-September/146282.html

Notice for Reviewers

This PEP was drafted in a separate repo:https://github.com/phouse512/peps/tree/pep-0526.

There was preliminary discussion on python-ideas and athttps://github.com/python/typing/issues/258.

Before you bring up an objection in a public forum please at leastread the summary ofrejected ideas listed at the end of this PEP.

Abstract

PEP 484 introduced type hints, a.k.a. type annotations. While itsmain focus was function annotations, it also introduced the notion oftype comments to annotate variables:

# 'primes' is a list of integersprimes=[]# type: List[int]# 'captain' is a string (Note: initial value is a problem)captain=...# type: strclassStarship:# 'stats' is a class variablestats={}# type: Dict[str, int]

This PEP aims at adding syntax to Python for annotating the types of variables(including class variables and instance variables),instead of expressing them through comments:

primes:List[int]=[]captain:str# Note: no initial value!classStarship:stats:ClassVar[Dict[str,int]]={}

PEP 484 explicitly states that type comments are intended to help withtype inference in complex cases, and this PEP does not change thisintention. However, since in practice type comments have also beenadopted for class variables and instance variables, this PEP alsodiscusses the use of type annotations for those variables.

Rationale

Although type comments work well enough, the fact that they’reexpressed through comments has some downsides:

  • Text editors often highlight comments differently from type annotations.
  • There’s no way to annotate the type of an undefined variable; one needs toinitialize it toNone (e.g.a=None#type:int).
  • Variables annotated in a conditional branch are difficult to read:
    ifsome_value:my_var=function()# type: Loggerelse:my_var=another_function()# Why isn't there a type here?
  • Since type comments aren’t actually part of the language, if a Python scriptwants to parse them, it requires a custom parser instead of just usingast.
  • Type comments are used a lot in typeshed. Migrating typeshed to usethe variable annotation syntax instead of type comments would improvereadability of stubs.
  • In situations where normal comments and type comments are used together, it isdifficult to distinguish them:
    path=None# type: Optional[str]  # Path to module source
  • It’s impossible to retrieve the annotations at runtime outside ofattempting to find the module’s source code and parse it at runtime,which is inelegant, to say the least.

The majority of these issues can be alleviated by making the syntaxa core part of the language. Moreover, having a dedicated annotation syntaxfor class and instance variables (in addition to method annotations) willpave the way to static duck-typing as a complement to nominal typing definedbyPEP 484.

Non-goals

While the proposal is accompanied by an extension of thetyping.get_type_hintsstandard library function for runtime retrieval of annotations, variableannotations are not designed for runtime type checking. Third party packageswill have to be developed to implement such functionality.

It should also be emphasized thatPython will remain a dynamically typedlanguage, and the authors have no desire to ever make type hints mandatory,even by convention. Type annotations should not be confused with variabledeclarations in statically typed languages. The goal of annotation syntax isto provide an easy way to specify structured type metadatafor third party tools.

This PEP does not require type checkers to change their type checkingrules. It merely provides a more readable syntax to replace typecomments.

Specification

Type annotation can be added to an assignment statement or to a singleexpression indicating the desired type of the annotation target to a thirdparty type checker:

my_var:intmy_var=5# Passes type check.other_var:int='a'# Flagged as error by type checker,# but OK at runtime.

This syntax does not introduce any new semantics beyondPEP 484, so thatthe following three statements are equivalent:

var=value# type: annotationvar:annotation;var=valuevar:annotation=value

Below we specify the syntax of type annotationsin different contexts and their runtime effects.

We also suggest how type checkers might interpret annotations, butcompliance to these suggestions is not mandatory. (This is in linewith the attitude towards compliance inPEP 484.)

Global and local variable annotations

The types of locals and globals can be annotated as follows:

some_number:int# variable without initial valuesome_list:List[int]=[]# variable with initial value

Being able to omit the initial value allows for easier typing of variablesassigned in conditional branches:

sane_world:boolif2+2==4:sane_world=Trueelse:sane_world=False

Note that, although the syntax does allow tuple packing, it doesnot allowone to annotate the types of variables when tuple unpacking is used:

# Tuple packing with variable annotation syntaxt:Tuple[int,...]=(1,2,3)# ort:Tuple[int,...]=1,2,3# This only works in Python 3.8+# Tuple unpacking with variable annotation syntaxheader:strkind:intbody:Optional[List[str]]header,kind,body=message

Omitting the initial value leaves the variable uninitialized:

a:intprint(a)# raises NameError

However, annotating a local variable will cause the interpreter to always makeit a local:

deff():a:intprint(a)# raises UnboundLocalError# Commenting out the a: int makes it a NameError.

as if the code were:

deff():ifFalse:a=0print(a)# raises UnboundLocalError

Duplicate type annotations will be ignored. However, static typecheckers may issue a warning for annotations of the same variableby a different type:

a:inta:str# Static type checker may or may not warn about this.

Class and instance variable annotations

Type annotations can also be used to annotate class and instance variablesin class bodies and methods. In particular, the value-less notationa:intallows one to annotate instance variables that should be initializedin__init__ or__new__. The proposed syntax is as follows:

classBasicStarship:captain:str='Picard'# instance variable with defaultdamage:int# instance variable without defaultstats:ClassVar[Dict[str,int]]={}# class variable

HereClassVar is a special class defined by the typing module thatindicates to the static type checker that this variable should not beset on instances.

Note that aClassVar parameter cannot include any type variables, regardlessof the level of nesting:ClassVar[T] andClassVar[List[Set[T]]] areboth invalid ifT is a type variable.

This could be illustrated with a more detailed example. In this class:

classStarship:captain='Picard'stats={}def__init__(self,damage,captain=None):self.damage=damageifcaptain:self.captain=captain# Else keep the defaultdefhit(self):Starship.stats['hits']=Starship.stats.get('hits',0)+1

stats is intended to be a class variable (keeping track of many differentper-game statistics), whilecaptain is an instance variable with a defaultvalue set in the class. This difference might not be seen by a typechecker: both get initialized in the class, butcaptain serves onlyas a convenient default value for the instance variable, whilestatsis truly a class variable – it is intended to be shared by all instances.

Since both variables happen to be initialized at the class level, it isuseful to distinguish them by marking class variables as annotated withtypes wrapped inClassVar[...]. In this way a type checker may flagaccidental assignments to attributes with the same name on instances.

For example, annotating the discussed class:

classStarship:captain:str='Picard'damage:intstats:ClassVar[Dict[str,int]]={}def__init__(self,damage:int,captain:str=None):self.damage=damageifcaptain:self.captain=captain# Else keep the defaultdefhit(self):Starship.stats['hits']=Starship.stats.get('hits',0)+1enterprise_d=Starship(3000)enterprise_d.stats={}# Flagged as error by a type checkerStarship.stats={}# This is OK

As a matter of convenience (and convention), instance variables can beannotated in__init__ or other methods, rather than in the class:

fromtypingimportGeneric,TypeVarT=TypeVar('T')classBox(Generic[T]):def__init__(self,content):self.content:T=content

Annotating expressions

The target of the annotation can be any valid single assignmenttarget, at least syntactically (it is up to the type checker what todo with this):

classCls:passc=Cls()c.x:int=0# Annotates c.x with int.c.y:int# Annotates c.y with int.d={}d['a']:int=0# Annotates d['a'] with int.d['b']:int# Annotates d['b'] with int.

Note that even a parenthesized name is considered an expression,not a simple name:

(x):int# Annotates x with int, (x) treated as expression by compiler.(y):int=0# Same situation here.

Where annotations aren’t allowed

It is illegal to attempt to annotate variables subject toglobalornonlocal in the same function scope:

deff():globalx:int# SyntaxErrordefg():x:int# Also a SyntaxErrorglobalx

The reason is thatglobal andnonlocal don’t own variables;therefore, the type annotations belong in the scope owning the variable.

Only single assignment targets and single right hand side values are allowed.In addition, one cannot annotate variables used in afor orwithstatement; they can be annotated ahead of time, in a similar manner to tupleunpacking:

a:intforainmy_iter:...f:MyFilewithmyfunc()asf:...

Variable annotations in stub files

As variable annotations are more readable than type comments, they arepreferred in stub files for all versions of Python, including Python 2.7.Note that stub files are not executed by Python interpreters, and thereforeusing variable annotations will not lead to errors. Type checkers shouldsupport variable annotations in stubs for all versions of Python. For example:

# file lib.pyiADDRESS:unicode=...classError:cause:Union[str,unicode]

Preferred coding style for variable annotations

Annotations for module level variables, class and instance variables,and local variables should have a single space after corresponding colon.There should be no space before the colon. If an assignment has right handside, then the equality sign should have exactly one space on both sides.Examples:

  • Yes:
    code:intclassPoint:coords:Tuple[int,int]label:str='<unknown>'
  • No:
    code:int# No space after coloncode:int# Space before colonclassTest:result:int=0# No spaces around equality sign

Changes to Standard Library and Documentation

  • A new covariant typeClassVar[T_co] is added to thetypingmodule. It accepts only a single argument that should be a valid type,and is used to annotate class variables that should not be set on classinstances. This restriction is ensured by static checkers,but not at runtime. See theclassvar section for examples and explanations for the usage ofClassVar, and see therejected sectionfor more information on the reasoning behindClassVar.
  • Functionget_type_hints in thetyping module will be extended,so that one can retrieve type annotations at runtime from modulesand classes as well as functions.Annotations are returned as a dictionary mapping from variable or argumentsto their type hints with forward references evaluated.For classes it returns a mapping (perhapscollections.ChainMap)constructed from annotations in method resolution order.
  • Recommended guidelines for using annotations will be added to thedocumentation, containing a pedagogical recapitulation of specificationsdescribed in this PEP and inPEP 484. In addition, a helper script fortranslating type comments into type annotations will be publishedseparately from the standard library.

Runtime Effects of Type Annotations

Annotating a local variable will causethe interpreter to treat it as a local, even if it was never assigned to.Annotations for local variables will not be evaluated:

deff():x:NonexistentName# No error.

However, if it is at a module or class level, then the typewill beevaluated:

x:NonexistentName# Error!classX:var:NonexistentName# Error!

In addition, at the module or class level, if the item being annotated is asimple name, then it and the annotation will be stored in the__annotations__ attribute of that module or class (mangled if private)as an ordered mapping from names to evaluated annotations.Here is an example:

fromtypingimportDictclassPlayer:...players:Dict[str,Player]__points:intprint(__annotations__)# prints: {'players': typing.Dict[str, __main__.Player],#          '_Player__points': <class 'int'>}

__annotations__ is writable, so this is permitted:

__annotations__['s']=str

But attempting to update__annotations__ to something other than anordered mapping may result in a TypeError:

classC:__annotations__=42x:int=5# raises TypeError

(Note that the assignment to__annotations__, which is theculprit, is accepted by the Python interpreter without questioning it– but the subsequent type annotation expects it to be aMutableMapping and will fail.)

The recommended way of getting annotations at runtime is by usingtyping.get_type_hints function; as with all dunder attributes,any undocumented use of__annotations__ is subject to breakagewithout warning:

fromtypingimportDict,ClassVar,get_type_hintsclassStarship:hitpoints:int=50stats:ClassVar[Dict[str,int]]={}shield:int=100captain:strdef__init__(self,captain:str)->None:...assertget_type_hints(Starship)=={'hitpoints':int,'stats':ClassVar[Dict[str,int]],'shield':int,'captain':str}assertget_type_hints(Starship.__init__)=={'captain':str,'return':None}

Note that if annotations are not found statically, then the__annotations__ dictionary is not created at all. Also thevalue of having annotations available locally does not offsetthe cost of having to create and populate the annotations dictionaryon every function call. Therefore, annotations at function level arenot evaluated and not stored.

Other uses of annotations

While Python with this PEP will not object to:

alice:'well done'='A+'bob:'what a shame'='F-'

since it will not care about the type annotation beyond “it evaluateswithout raising”, a type checker that encounters it will flag it,unless disabled with#type:ignore or@no_type_check.

However, since Python won’t care what the “type” is,if the above snippet is at the global level or in a class,__annotations__will include{'alice':'welldone','bob':'whatashame'}.

These stored annotations might be used for other purposes,but with this PEP we explicitly recommend type hinting as thepreferred use of annotations.

Rejected/Postponed Proposals

  • Should we introduce variable annotations at all?Variable annotations havealready been around for almost two yearsin the form of type comments, sanctioned byPEP 484. They areextensively used by third party type checkers (mypy, pytype,PyCharm, etc.) and by projects using the type checkers. However, thecomment syntax has many downsides listed in Rationale. This PEP isnot about the need for type annotations, it is about what should bethe syntax for such annotations.
  • Introduce a new keyword:The choice of a good keyword is hard,e.g. it can’t bevar because that is way too common a variable name,and it can’t belocal if we want to use it for class variables orglobals. Second, no matter what we choose, we’d still needa__future__ import.
  • Usedefas a keyword:The proposal would be:
    defprimes:List[int]=[]defcaptain:str

    The problem with this is thatdef means “define a function” togenerations of Python programmers (and tools!), and using it also todefine variables does not increase clarity. (Though this is ofcourse subjective.)

  • Use function based syntax:It was proposed to annotate types of variables usingvar=cast(annotation[,value]). Although this syntaxalleviates some problems with type comments like absence of the annotationin AST, it does not solve other problems such as readabilityand it introduces possible runtime overhead.
  • Allow type annotations for tuple unpacking:This causes ambiguity: it’s not clear what this statement means:
    x,y:T

    Arex andy both of typeT, or do we expectT to bea tuple type of two items that are distributed overx andy,or perhapsx has typeAny andy has typeT? (Thelatter is what this would mean if this occurred in a functionsignature.) Rather than leave the (human) reader guessing, weforbid this, at least for now.

  • Parenthesized form(var:type)for annotations:It was brought up on python-ideas as a remedy for the above-mentionedambiguity, but it was rejected since such syntax would be hairy,the benefits are slight, and the readability would be poor.
  • Allow annotations in chained assignments:This has problems of ambiguity and readability similar to tupleunpacking, for example in:
    x:int=y=1z=w:int=1

    it is ambiguous, what should the types ofy andz be?Also the second line is difficult to parse.

  • Allow annotations inwithandforstatement:This was rejected because infor it would make it hard to spot the actualiterable, and inwith it would confuse the CPython’s LL(1) parser.
  • Evaluate local annotations at function definition time:This has been rejected by Guido because the placement of the annotationstrongly suggests that it’s in the same scope as the surrounding code.
  • Store variable annotations also in function scope:The value of having the annotations available locally is just not enoughto significantly offset the cost of creating and populating the dictionaryoneach function call.
  • Initialize variables annotated without assignment:It was proposed on python-ideas to initializex inx:int toNone or to an additional special constant like Javascript’sundefined. However, adding yet another singleton value to the languagewould needed to be checked for everywhere in the code. Therefore,Guido just said plain “No” to this.
  • Add alsoInstanceVarto the typing module:This is redundant because instance variables are way more common thanclass variables. The more common usage deserves to be the default.
  • Allow instance variable annotations only in methods:The problem is that many__init__ methods do a lot of things besidesinitializing instance variables, and it would be harder (for a human)to find all the instance variable annotations.And sometimes__init__ is factored into more helper methodsso it’s even harder to chase them down. Putting the instance variableannotations together in the class makes it easier to find them,and helps a first-time reader of the code.
  • Use syntaxx:classt=vfor class variables:This would require a more complicated parser and theclasskeyword would confuse simple-minded syntax highlighters. Anyway weneed to haveClassVar store class variables to__annotations__, so a simpler syntax was chosen.
  • Forget aboutClassVaraltogether:This was proposed since mypy seems to be getting along fine without a wayto distinguish between class and instance variables. But a type checkercan do useful things with the extra information, for example flagaccidental assignments to a class variable via the instance(which would create an instance variable shadowing the class variable).It could also flag instance variables with mutable defaults,a well-known hazard.
  • UseClassAttrinstead ofClassVar:The main reason whyClassVar is better is following: many things areclass attributes, e.g. methods, descriptors, etc. But only specificattributes are conceptually class variables (or maybe constants).
  • Do not evaluate annotations, treat them as strings:This would be inconsistent with the behavior of function annotations thatare always evaluated. Although this might be reconsidered in future,it was decided inPEP 484 that this would have to be a separate PEP.
  • Annotate variable types in class docstring:Many projects already use various docstring conventions, often withoutmuch consistency and generally without conforming to thePEP 484 annotationsyntax yet. Also this would require a special sophisticated parser.This, in turn, would defeat the purpose of the PEP –collaborating with the third party type checking tools.
  • Implement__annotations__as a descriptor:This was proposed to prohibit setting__annotations__ to somethingnon-dictionary or non-None. Guido has rejected this idea as unnecessary;instead a TypeError will be raised if an attempt is made to update__annotations__ when it is anything other than a mapping.
  • Treating bare annotations the same as global or nonlocal:The rejected proposal would prefer that the presence of anannotation without assignment in a function body should not involveany evaluation. In contrast, the PEP implies that if the targetis more complex than a single name, its “left-hand part” should beevaluated at the point where it occurs in the function body, just toenforce that it is defined. For example, in this example:
    deffoo(self):slef.name:str

    the nameslef should be evaluated, just so that if it is notdefined (as is likely in this example :-), the error will be caughtat runtime. This is more in line with what happens when thereisan initial value, and thus is expected to lead to fewer surprises.(Also note that if the target wasself.name (this time correctlyspelled :-), an optimizing compiler has no obligation to evaluateself as long as it can prove that it will definitely bedefined.)

Backwards Compatibility

This PEP is fully backwards compatible.

Implementation

An implementation for Python 3.6 can be foundon GitHub.

Copyright

This document has been placed in the public domain.


Source:https://github.com/python/peps/blob/main/peps/pep-0526.rst

Last modified:2025-02-01 08:59:27 GMT


[8]ページ先頭

©2009-2025 Movatter.jp