Mon 5 Feb 2007
The two PEPs I talked about in the last two entries have just gone live. The PEPs (3109, 3110) are more formal and more comprehensive versions of the blog entries, so if you read those posts, you’ll have the basic ideas.
Mon 5 Feb 2007
The two PEPs I talked about in the last two entries have just gone live. The PEPs (3109, 3110) are more formal and more comprehensive versions of the blog entries, so if you read those posts, you’ll have the basic ideas.
Thu 25 Jan 2007
Following up on my last post about catching exceptions in Python 3, here are some excerpts from the companion PEP I’m working on, which addresses “raise” statements.
There are simply too many forms to the raise statement in Python 2. Quoting from the reference manual:
If no expressions are present, raise re-raises the last exception that was active in the current scope…
Otherwise, raise evaluates the expressions to get three objects, using None as the value of omitted expressions. The first two objects are used to determine the type and value of the exception.
If the first object is an instance, the type of the exception is the class of the instance, the instance itself is the value, and the second object must be None.
If the first object is a class, it becomes the type of the exception. The second object is used to determine the exception value: If it is an instance of the class, the instance becomes the exception value. If the second object is a tuple, it is used as the argument list for the class constructor; if it is None, an empty argument list is used, and any other object is treated as a single argument to the constructor. The instance so created by calling the constructor is used as the exception value.
If a third object is present and not None, it must be a traceback object…and it is substituted instead of the current location as the place where the exception occurred… The three-expression form of raise is useful to re-raise an exception transparently in an except clause, but raise with no expressions should be preferred if the exception to be re-raised was the most recently active exception in the current scope.
That’s pretty complex, and it doesn’t even address string exceptions. Until I started digging around in the interpreter internals, I didn’t even know the three-object form was possible. Here’s what raise will look like in Python 3:
raise (with no arguments) is used to re-raise the active exception in an except block.
raise EXCEPTION is used to raise a new exception. This form has two sub-variants: EXCEPTION may be either an instance of BaseException or a subclass of BaseException (follows from PEP 352). If EXCEPTION is a subclass, it will be called with no arguments to obtain an exception instance.
To raise anything else is an error.
“But wait! That doesn’t allow me to supply a traceback!”. Never fear, PEP 344 is here. It specifies that exceptions will grow a __traceback__ attribute, and this is how we’ll be able to raise exceptions with arbitrary tracebacks. What looked like this in Python 2
raise Type, Value, Traceback
will look like this in Python 3
e = Type(Value) e.__traceback__ = Traceback raise e
Or possibly this (per a suggestion from Guido):
raise Type(Value).set_traceback(Traceback)
I’m also relying on PEP 344 to replace Python 2’s raise Type, Instance variant. This is most often used to “cast” an exception instance from one type to another, such as this example from distutils.bcppcompiler:
try:
self.spawn (['brcc32', '-fo', obj, src])
except DistutilsExecError, msg:
raise CompileError, msg
PEP 344 introduces a raise ... from ... statement and a corresponding __cause__ attribute. Taking advantage of these new tools, the above Python 2 snippet translates to
try:
self.spawn (['brcc32', '-fo', obj, src])
except DistutilsExecError as msg:
raise CompileError from msg
While the main thrust of this work is to reduce the size of the language — the number of details and nuances you have to keep track of — there’s a more tangible benefit, as pointed out by A. M. Kuchling:
PEP 8 doesn’t express any preference between the two forms of raise statements:
raise ValueError, 'blah' raise ValueError('blah')I like the second form better, because if the exception arguments are long or include string formatting, you don’t need to use line continuation characters because of the containing parens.
Less line noise, a smaller language; what’s not to like?
Wed 24 Jan 2007
Lately, I’ve been working on a PEP to change how Python 3’s “except” statements work. The highlights:
(Anyone wanting to discuss these should join the python-3000 list and comment there.)
The grammar for “except” statements will change from
except_clause: 'except' [test [',' test]]
in Python 2 to
except_clause: 'except' [test ['as' NAME]]
in Python 3. This is being done to eliminate a syntactic ambiguity where the parser can’t tell whether
except EXPRESSION, EXPRESSION:
should be interpreted as
except TYPE, TYPE:
or
except TYPE, TARGET:
Python 2 opts for the latter semantic, at the cost of requiring the former to be parenthesized.
Converting Python 2-style “except” statements to Python 3 can be handled automatically (for the most part) by Guido van Rossum’s 2to3 utility.
As specified in PEP 352, the ability to treat exceptions as tuples will be removed, meaning this code will no longer work:
except os.error, (errno, errstr):
Because the automatic unpacking will no longer be possible by default, the ability to use tuples as “except” targets at all will be removed.
PEP 344 specifies that exception instances in Python 3 will possess a __traceback__ attribute. The Open Issues section of that PEP includes a paragraph on garbage collection difficulties caused by this attribute, namely a “exception -> traceback -> stack frame -> exception” reference cycle, whereby all locals are kept in scope until the next GC run. Python 3 will resolve this issue by making sure the target name is deleted at the end of the “except” suite, thus breaking the cycle.
This will be done by having the compiler emit appropriate bytecode to translate
try:
try_body
except E as N:
except_body
...
to this (in Python 2.5 terms):
try:
try_body
except E, N:
try:
except_body
finally:
N = None
del N
...
An implementation of this has already been checked into the p3yk [sic] branch.
Thu 4 Jan 2007
To anyone planning to email me about how much you hate the syntax for return value annotations: don’t. Guido wants the -> arrow, so the arrow is what we’re getting. Your ideas for using returns or return or whatever else have already occurred to others — namely me — and were rejected months ago.
Guido’s the one you have to convince, not me, and he’s already made up his mind.
Wed 3 Jan 2007
Based on this python-3000 thread and a number of off-list emails, I’m dropping my earlier objection to PEP 3107. I hadn’t been convinced that there was a sufficiently broad spectrum of use-cases for function annotations to justify changing Python’s syntax. A bunch of people came out of the woodwork with viable uses for annotations, which is what I was looking for. Accordingly, I’ll be working up a patch to PEP 3107 to include a “Use Cases” section.
Thanks to everyone who emailed or commented, especially Phillip J. Eby, who led the python-3000 effort to convince me.
Tue 2 Jan 2007
A blogified version of a python-3000 post, in which the author of PEP 3107 revels in situational irony.
I was explaining function annotations to a friend this past weekend and found that, even though I had written a PEP on the subject and spent months debating the little details of “how are we going to make annotations work?”, I was hard-pressed to answer the question of “why are we doing this?”
The biggest problem I faced — then and now — is justifying the use-cases for annotations. Here’re the use-cases I could come up with off the top of my head: information for typecheckers; doc strings for parameters; extra information for IDEs; extra information for static analysis tools like pylint. These can all be addressed together:
Are the users clamoring for these things? Do these address real problems that users are having?
Not to my knowledge.
In a recent python-ideas post, Guido van Rossum said that “Collin’s existing type annotation library … could be made more elegant by attaching the types directly to the arguments”. As far as I can tell, the only gains in elegance are that you don’t have to repeat the names of a function’s parameters in the typechecking decorator. None of my users have ever complained about this tiny bit of repetition, and I’ve never felt it an undue burden in my own usage.
It could even be considered an advantage, since including the “annotations” in the typechecking decorator means all I have to do to remove typechecking from a function is delete a single line, rather than pick through a function’s declaration, removing the relevant bits.
def foo(a: 'the object to be frobnicated',
b=7: 'this controls the level of frobnication',
c='Rojo': 'the name of a color, in Spanish, that should
be applied to the
frobnicated thing') -> 'Returns an integer between
9 and 13':
''' Frobnicate an object in a Spanish way '''
...
What does this accomplish that can’t be achieved with any of the standard documentation syntaxes in existence today?
I can see it being genuinely useful to be able to get parameter/return type information in a tooltip message. But IDLE can do this already, without annotations:
Quoting Nick Coghlan, from August 2006: “annotations wouldn’t be useful for tools like pychecker … to be really useful for a tool like pychecker they’d have to be ubiquitous, and that’s really not Python any more”. Agreed.
I could say You Aren’t Going to Need It, but that gets the tense wrong; we’re getting along without annotations quite nicely here in the present. In short: I think that PEP 3107 be rejected as an overly-specific, unnecessary addition to the language.
Anyone with thoughts on or responses to this article should post them to the python-3000 mailing list.
Fri 29 Dec 2006
My very first PEP, fresh and piping-hot:
| PEP: | 3107 |
|---|---|
| Title: | Function Annotations |
| Version: | 53169 |
| Last-Modified: | 2006-12-27 22:59:16 -0600 (Wed, 27 Dec 2006) |
| Author: | Collin Winter <collinw at gmail.com>, Tony Lownds <tony at lownds.com> |
| Status: | Draft |
| Type: | Standards Track |
| Requires: | 362 |
| Content-Type: | text/x-rst |
| Created: | 2-Dec-2006 |
| Python-Version: | 3.0 |
| Post-History: |
This PEP introduces a syntax for adding arbitrary metadata annotations
to Python functions [1].
Because Python’s 2.x series lacks a standard way of annotating a
function’s parameters and return values (e.g., with information about
what type a function’s return value should be), a variety of tools
and libraries have appeared to fill this gap [2]. Some
utilise the decorators introduced in "PEP 318", while others parse a
function’s docstring, looking for annotations there.
This PEP aims to provide a single, standard way of specifying this
information, reducing the confusion caused by the wide variation in
mechanism and syntax that has existed until this point.
Before launching into a discussion of the precise ins and outs of
Python 3.0’s function annotations, let’s first talk broadly about
what annotations are and are not:
Function annotations, both for parameters and return values, are
completely optional.
Function annotations are nothing more than a way of associating
arbitrary Python expressions with various parts of a function at
compile-time.
By itself, Python does not attach any particular meaning or
significance to annotations. Left to its own, Python simply makes
these expressions available as described in Accessing Function
Annotations below.
The only way that annotations take on meaning is when they are
interpreted by third-party libraries. These annotation consumers
can do anything they want with a function’s annotations. For
example, one library might use string-based annotations to provide
improved help messages, like so:
def compile(source: "something compilable",
filename: "where the compilable thing comes from",
mode: "is this a single statement or a suite?"):
...
Another library might be used to provide typechecking for Python
functions and methods. This library could use annotations to
indicate the function’s expected input and return types, possibly
something like:
def haul(item: Haulable, *vargs: PackAnimal) -> Distance:
...
However, neither the strings in the first example nor the
type information in the second example have any meaning on their
own; meaning comes from third-party libraries alone.
Following from point 2, this PEP makes no attempt to introduce
any kind of standard semantics, even for the built-in types.
This work will be left to third-party libraries.
There is no worry that these libraries will assign semantics at
random, or that a variety of libraries will appear, each with
varying semantics and interpretations of what, say, a tuple of
strings means. The difficulty inherent in writing annotation
interpreting libraries will keep their number low and their
authorship in the hands of people who, frankly, know what they’re
doing.
Annotations for parameters take the form of optional expressions that
follow the parameter name. This example indicates that parameters
‘a’ and ‘c’ should both be an int, while parameter ‘b’ should
be a dict:
def foo(a: int, b: dict, c: int = 5):
...
In pseudo-grammar, parameters now look like identifier [:
expression] [= expression]. That is, annotations always precede a
parameter’s default value and both annotations and default values are
optional. Just like how equal signs are used to indicate a default
value, colons are used to mark annotations. All annotation
expressions are evaluated when the function definition is executed.
Annotations for excess parameters (i.e., *args and **kwargs)
are indicated similarly. In the following function definition,
*args is flagged as a tuple of int, and **kwargs is
marked as a dict whose keys are strings and whose values are of type
str:
def foo(*args: int, **kwargs: str):
...
Note that, depending on what annotation-interpreting library you’re
using, the following might also be a valid spelling of the above:
def foo(*args: [int], **kwargs: {str: str}):
...
Only the first, however, has the BDFL’s blessing [3] as
the One Obvious Way.
The examples thus far have omitted examples of how to annotate the
type of a function’s return value. This is done like so:
def sum(*args: int) -> int:
...
The parameter list can now be followed by a literal -> and a
Python expression. Like the annotations for parameters, this
expression will be evaluated when the function definition is executed.
The grammar for function definitions [11] is now:
decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
decorators: decorator+
funcdef: [decorators] 'def' NAME parameters ['->' test] ':' suite
parameters: '(' [typedargslist] ')'
typedargslist: ((tfpdef ['=' test] ',')*
('*' [tname] (',' tname ['=' test])* [',' '**' tname]
| '**' tname)
| tfpdef ['=' test] (',' tfpdef ['=' test])* [','])
tname: NAME [':' test]
tfpdef: tname | '(' tfplist ')'
tfplist: tfpdef (',' tfpdef)* [',']
lambda’s syntax does not support annotations. The syntax of
lambda could be changed to support annotations, by requiring
parentheses around the parameter list. However it was decided
[12] not to make this change because:
Once compiled, a function’s annotations are available via the
function’s func_annotations attribute. This attribute is
a dictionary, mapping parameter names to an object representing
the evaluated annotation expression
There is a special key in the func_annotations mapping,
"return". This key is present only if an annotation was supplied
for the function’s return value.
For example, the following annotation:
def foo(a: 'x', b: 5 + 6, c: list) -> str:
...
would result in a func_annotation mapping of
{'a': 'x',
'b': 11,
'c': list,
'return': str}
The return key was chosen because it cannot conflict with the name
of a parameter; any attempt to use return as a parameter name
would result in a SyntaxError.
func_annotations is an empty dictionary if no there are no
annotations on the function. func_annotations is always an empty
dictionary for functions created from lambda expressions.
The pydoc module should display the function annotations when
displaying help for a function. The inspect module should change
to support annotations.
Function Signature Objects should expose the function’s annotations.
The Parameter object may change or other changes may be warranted.
A sample implementation for the syntax changes has been provided
[10] by Tony Lownds.
| [1] | Unless specifically stated, "function" is generally used as a synonym for "callable" throughout this document. |
| [2] | The author’s typecheck [14] library makes use of decorators, while Maxime Bourget’s own typechecker [15] utilises parsed docstrings. |
| [3] | http://mail.python.org/pipermail/python-3000/2006-May/002173.html |
| [4] | http://mail.python.org/pipermail/python-3000/2006-May/002103.html |
| [5] | http://mail.python.org/pipermail/python-3000/2006-May/002091.html |
| [6] | http://mail.python.org/pipermail/python-3000/2006-May/001972.html |
| [7] | http://mail.python.org/pipermail/python-3000/2006-May/002105.html |
| [8] | http://mail.python.org/pipermail/python-3000/2006-May/002209.html |
| [9] | http://mail.python.org/pipermail/python-3000/2006-June/002438.html |
| [10] | http://python.org/sf/1607548 |
| [11] | http://www.python.org/doc/current/ref/function.html |
| [12] | http://mail.python.org/pipermail/python-3000/2006-May/001613.html |
| [13] | http://www.python.org/dev/peps/pep-0362/ |
| [14] | http://oakwinter.com/code/typecheck/ |
| [15] | http://maxrepo.info/taxonomy/term/3,6/all |
This document has been placed in the public domain.