PEP 463: More updates by Chris Angelico
This commit is contained in:
parent
3baaf22f53
commit
33340f76c2
197
pep-0463.txt
197
pep-0463.txt
|
@ -41,9 +41,6 @@ support this.
|
|||
* min(sequence, default=default) - keyword argument in place of
|
||||
ValueError
|
||||
|
||||
* sum(sequence, start=default) - slightly different but can do the
|
||||
same job
|
||||
|
||||
* statistics.mean(data) - no way to handle an empty iterator
|
||||
|
||||
|
||||
|
@ -131,11 +128,11 @@ This currently works::
|
|||
The proposal adds this::
|
||||
|
||||
lst = [1, 2]
|
||||
value = lst[2] except IndexError: "No value"
|
||||
value = (lst[2] except IndexError: "No value")
|
||||
|
||||
Specifically, the syntax proposed is::
|
||||
|
||||
expr except exception_list: default
|
||||
(expr except exception_list: default)
|
||||
|
||||
where expr, exception_list, and default are all expressions. First,
|
||||
expr is evaluated. If no exception is raised, its value is the value
|
||||
|
@ -146,6 +143,12 @@ result in the corresponding default expression being evaluated and
|
|||
becoming the value of the expression. As with the statement form of
|
||||
try/except, non-matching exceptions will propagate upward.
|
||||
|
||||
Parentheses are required around the entire expression, unless they
|
||||
would be completely redundant, according to the same rules as generator
|
||||
expressions follow. This guarantees correct interpretation of nested
|
||||
except-expressions, and allows for future expansion of the syntax -
|
||||
see below on multiple except clauses.
|
||||
|
||||
Note that the current proposal does not allow the exception object to
|
||||
be captured. Where this is needed, the statement form must be used.
|
||||
(See below for discussion and elaboration on this.)
|
||||
|
@ -206,20 +209,39 @@ All forms involving the 'as' capturing clause have been deferred from
|
|||
this proposal in the interests of simplicity, but are preserved in the
|
||||
table above as an accurate record of suggestions.
|
||||
|
||||
The four forms most supported by this proposal are, in order::
|
||||
|
||||
Open Issues
|
||||
===========
|
||||
value = (expr except Exception: default)
|
||||
value = (expr except Exception -> default)
|
||||
value = (expr except Exception pass default)
|
||||
value = (expr except Exception then default)
|
||||
|
||||
Parentheses around the entire expression
|
||||
----------------------------------------
|
||||
All four maintain left-to-right evaluation order: first the base expression,
|
||||
then the exception list, and lastly the default. This is important, as the
|
||||
expressions are evaluated lazily. By comparison, several of the ad-hoc
|
||||
alternatives listed above must (by the nature of functions) evaluate their
|
||||
default values eagerly. The preferred form, using the colon, parallels
|
||||
try/except by using "except exception_list:", and parallels lambda by having
|
||||
"keyword name_list: subexpression". Using the arrow introduces a token many
|
||||
programmers will not be familiar with, and which currently has no similar
|
||||
meaning, but is otherwise quite readable. The English word "pass" has a
|
||||
vaguely similar meaning (consider the common usage "pass by value/reference"
|
||||
for function arguments), and "pass" is already a keyword, but as its meaning
|
||||
is distinctly unrelated, this may cause confusion. Using "then" makes sense
|
||||
in English, but this introduces a new keyword to the language - albeit one
|
||||
not in common use, but a new keyword all the same.
|
||||
|
||||
Generator expressions require parentheses, unless they would be
|
||||
strictly redundant. Ambiguities with except expressions could be
|
||||
resolved in the same way, forcing nested except-in-except trees to be
|
||||
correctly parenthesized and requiring that the outer expression be
|
||||
clearly delineated. `Steven D'Aprano elaborates on the issue.`__
|
||||
Left to right evaluation order is extremely important to readability, as it
|
||||
parallels the order most expressions are evaluated. Alternatives such as::
|
||||
|
||||
__ https://mail.python.org/pipermail/python-ideas/2014-February/025647.html
|
||||
value = (expr except default if Exception)
|
||||
|
||||
break this, by first evaluating the two ends, and then coming to the middle;
|
||||
while this may not seem terrible (as the exception list will usually be a
|
||||
constant), it does add to the confusion when multiple clauses meet, either
|
||||
with multiple except/if or with the existing if/else, or a combination.
|
||||
Using the preferred order, subexpressions will always be evaluated from
|
||||
left to right, no matter how the syntax is nested.
|
||||
|
||||
|
||||
Example usage
|
||||
|
@ -236,7 +258,7 @@ Many of these patterns are extremely common.
|
|||
|
||||
Retrieve an argument, defaulting to None::
|
||||
|
||||
cond = args[1] except IndexError: None
|
||||
cond = (args[1] except IndexError: None)
|
||||
|
||||
# Lib/pdb.py:803:
|
||||
try:
|
||||
|
@ -246,7 +268,7 @@ Retrieve an argument, defaulting to None::
|
|||
|
||||
Fetch information from the system if available::
|
||||
|
||||
pwd = os.getcwd() except OSError: None
|
||||
pwd = (os.getcwd() except OSError: None)
|
||||
|
||||
# Lib/tkinter/filedialog.py:210:
|
||||
try:
|
||||
|
@ -256,7 +278,7 @@ Fetch information from the system if available::
|
|||
|
||||
Attempt a translation, falling back on the original::
|
||||
|
||||
e.widget = self._nametowidget(W) except KeyError: W
|
||||
e.widget = (self._nametowidget(W) except KeyError: W)
|
||||
|
||||
# Lib/tkinter/__init__.py:1222:
|
||||
try:
|
||||
|
@ -267,7 +289,7 @@ Attempt a translation, falling back on the original::
|
|||
Read from an iterator, continuing with blank lines once it's
|
||||
exhausted::
|
||||
|
||||
line = readline() except StopIteration: ''
|
||||
line = (readline() except StopIteration: '')
|
||||
|
||||
# Lib/lib2to3/pgen2/tokenize.py:370:
|
||||
try:
|
||||
|
@ -280,7 +302,7 @@ this particular example could be taken further, turning a series of
|
|||
separate assignments into a single large dict initialization::
|
||||
|
||||
# sys.abiflags may not be defined on all platforms.
|
||||
_CONFIG_VARS['abiflags'] = sys.abiflags except AttributeError: ''
|
||||
_CONFIG_VARS['abiflags'] = (sys.abiflags except AttributeError: '')
|
||||
|
||||
# Lib/sysconfig.py:529:
|
||||
try:
|
||||
|
@ -292,7 +314,7 @@ separate assignments into a single large dict initialization::
|
|||
Retrieve an indexed item, defaulting to None (similar to dict.get)::
|
||||
|
||||
def getNamedItem(self, name):
|
||||
return self._attrs[name] except KeyError: None
|
||||
return (self._attrs[name] except KeyError: None)
|
||||
|
||||
# Lib/xml/dom/minidom.py:573:
|
||||
def getNamedItem(self, name):
|
||||
|
@ -303,8 +325,8 @@ Retrieve an indexed item, defaulting to None (similar to dict.get)::
|
|||
|
||||
Translate numbers to names, falling back on the numbers::
|
||||
|
||||
g = grp.getgrnam(tarinfo.gname)[2] except KeyError: tarinfo.gid
|
||||
u = pwd.getpwnam(tarinfo.uname)[2] except KeyError: tarinfo.uid
|
||||
g = (grp.getgrnam(tarinfo.gname)[2] except KeyError: tarinfo.gid)
|
||||
u = (pwd.getpwnam(tarinfo.uname)[2] except KeyError: tarinfo.uid)
|
||||
|
||||
# Lib/tarfile.py:2198:
|
||||
try:
|
||||
|
@ -319,7 +341,7 @@ Translate numbers to names, falling back on the numbers::
|
|||
Perform some lengthy calculations in EAFP mode, handling division by
|
||||
zero as a sort of sticky NaN::
|
||||
|
||||
value = calculate(x) except ZeroDivisionError: float("nan")
|
||||
value = (calculate(x) except ZeroDivisionError: float("nan"))
|
||||
|
||||
try:
|
||||
value = calculate(x)
|
||||
|
@ -328,38 +350,13 @@ zero as a sort of sticky NaN::
|
|||
|
||||
Calculate the mean of a series of numbers, falling back on zero::
|
||||
|
||||
value = statistics.mean(lst) except statistics.StatisticsError: 0
|
||||
value = (statistics.mean(lst) except statistics.StatisticsError: 0)
|
||||
|
||||
try:
|
||||
value = statistics.mean(lst)
|
||||
except statistics.StatisticsError:
|
||||
value = 0
|
||||
|
||||
Retrieving a message from either a cache or the internet, with auth
|
||||
check::
|
||||
|
||||
logging.info("Message shown to user: %s",((cache[k]
|
||||
except LookupError:
|
||||
(backend.read(k) except OSError: 'Resource not available')
|
||||
)
|
||||
if check_permission(k) else 'Access denied'
|
||||
) except BaseException: "This is like a bare except clause")
|
||||
|
||||
try:
|
||||
if check_permission(k):
|
||||
try:
|
||||
_ = cache[k]
|
||||
except LookupError:
|
||||
try:
|
||||
_ = backend.read(k)
|
||||
except OSError:
|
||||
_ = 'Resource not available'
|
||||
else:
|
||||
_ = 'Access denied'
|
||||
except BaseException:
|
||||
_ = "This is like a bare except clause"
|
||||
logging.info("Message shown to user: %s", _)
|
||||
|
||||
Looking up objects in a sparse list of overrides::
|
||||
|
||||
(overrides[x] or default except IndexError: default).ping()
|
||||
|
@ -419,11 +416,19 @@ Becomes::
|
|||
|
||||
status.append('%s:%d' % self.addr except TypeError: repr(self.addr))
|
||||
|
||||
In each case, the narrowed scope of the try/except ensures that an unexpected
|
||||
exception (for instance, AttributeError if "append" were misspelled) does not
|
||||
get caught by the same handler. This is sufficiently unlikely to be reason
|
||||
to break the call out into a separate line (as per the five line example
|
||||
above), but it is a small benefit gained as a side-effect of the conversion.
|
||||
|
||||
|
||||
Comparisons with other languages
|
||||
================================
|
||||
|
||||
(With thanks to Andrew Barnert for compiling this section.)
|
||||
(With thanks to Andrew Barnert for compiling this section. Note that the
|
||||
examples given here do not reflect the current version of the proposal,
|
||||
and need to be edited.)
|
||||
|
||||
`Ruby's`__ "begin…rescue…rescue…else…ensure…end" is an expression
|
||||
(potentially with statements inside it). It has the equivalent of an "as"
|
||||
|
@ -611,23 +616,18 @@ An examination of use-cases shows that this is not needed as often as
|
|||
it would be with the statement form, and as its syntax is a point on
|
||||
which consensus has not been reached, the entire feature is deferred.
|
||||
|
||||
In order to ensure compatibility with future versions, ensure that any
|
||||
consecutive except operators are parenthesized to guarantee the
|
||||
interpretation you expect.
|
||||
|
||||
Multiple 'except' keywords can be used, and they will all catch
|
||||
exceptions raised in the original expression (only)::
|
||||
|
||||
# Will catch any of the listed exceptions thrown by expr;
|
||||
# any exception thrown by a default expression will propagate.
|
||||
value = (expr
|
||||
except Exception1 [as e]: default1
|
||||
except Exception2 [as e]: default2
|
||||
# ... except ExceptionN [as e]: defaultN
|
||||
except Exception1: default1
|
||||
except Exception2: default2
|
||||
# ... except ExceptionN: defaultN
|
||||
)
|
||||
|
||||
Using parentheses to force an alternative interpretation works as
|
||||
expected::
|
||||
Currently, one of the following forms must be used::
|
||||
|
||||
# Will catch an Exception2 thrown by either expr or default1
|
||||
value = (
|
||||
|
@ -639,69 +639,35 @@ expected::
|
|||
(default1 except Exception2: default2)
|
||||
)
|
||||
|
||||
This last form is confusing and should be discouraged by PEP 8, but it
|
||||
is syntactically legal: you can put any sort of expression inside a
|
||||
ternary-except; ternary-except is an expression; therefore you can put
|
||||
a ternary-except inside a ternary-except.
|
||||
|
||||
Open question: Where there are multiple except clauses, should they be
|
||||
separated by commas? It may be easier for the parser, that way::
|
||||
|
||||
value = (expr
|
||||
except Exception1 [as e]: default1,
|
||||
except Exception2 [as e]: default2,
|
||||
# ... except ExceptionN [as e]: defaultN,
|
||||
)
|
||||
|
||||
with an optional comma after the last, as per tuple rules. Downside:
|
||||
Omitting the comma would be syntactically valid, and would have almost
|
||||
identical semantics, but would nest the entire preceding expression in
|
||||
its exception catching rig - a matching exception raised in the
|
||||
default clause would be caught by the subsequent except clause. As
|
||||
this difference is so subtle, it runs the risk of being a major bug
|
||||
magnet.
|
||||
|
||||
As a mitigation of this risk, this form::
|
||||
|
||||
value = expr except Exception1: default1 except Exception2: default2
|
||||
|
||||
could be syntactically forbidden, and parentheses required if the
|
||||
programmer actually wants that behaviour::
|
||||
|
||||
value = (expr except Exception1: default1) except Exception2: default2
|
||||
|
||||
This would prevent the accidental omission of a comma from changing
|
||||
the expression's meaning.
|
||||
Listing multiple exception clauses without parentheses is a syntax error
|
||||
(see above), and so a future version of Python is free to add this feature
|
||||
without breaking any existing code.
|
||||
|
||||
|
||||
Capturing the exception object
|
||||
------------------------------
|
||||
|
||||
In a try/except block, the use of 'as' to capture the exception object
|
||||
creates a local name binding, and implicitly deletes that binding in a
|
||||
finally clause. As 'finally' is not a part of this proposal (see
|
||||
below), this makes it tricky to describe; also, this use of 'as' gives
|
||||
a way to create a name binding in an expression context. Should the
|
||||
default clause have an inner scope in which the name exists, shadowing
|
||||
anything of the same name elsewhere? Should it behave the same way the
|
||||
statement try/except does, and unbind the name? Should it bind the
|
||||
name and leave it bound? (Almost certainly not; this behaviour was
|
||||
changed in Python 3 for good reason.)
|
||||
creates a local name binding, and implicitly deletes that binding (to
|
||||
avoid creating a reference loop) in a finally clause. In an expression
|
||||
context, this makes little sense, and a proper sub-scope would be
|
||||
required to safely capture the exception object - something akin to the
|
||||
way a list comprehension is handled. However, CPython currently
|
||||
implements a comprehension's subscope with a nested function call, which
|
||||
has consequences in some contexts such as class definitions, and is
|
||||
therefore unsuitable for this proposal. Should there be, in future, a
|
||||
way to create a true subscope (which could simplify comprehensions,
|
||||
except expressions, with blocks, and possibly more), then this proposal
|
||||
could be revived; until then, its loss is not a great one, as the simple
|
||||
exception handling that is well suited to the expression notation used
|
||||
here is generally concerned only with the type of the exception, and not
|
||||
its value - further analysis below.
|
||||
|
||||
Additionally, this syntax would allow a convenient way to capture
|
||||
This syntax would, admittedly, allow a convenient way to capture
|
||||
exceptions in interactive Python; returned values are captured by "_",
|
||||
but exceptions currently are not. This could be spelled::
|
||||
|
||||
>>> expr except Exception as e: e
|
||||
|
||||
(The inner scope idea is tempting, but currently CPython handles list
|
||||
comprehensions with a nested function call, as this is considered
|
||||
easier. It may be of value to simplify both comprehensions and except
|
||||
expressions, but that is a completely separate proposal to this PEP;
|
||||
alternatively, it may be better to stick with what's known to
|
||||
work. `Nick Coghlan elaborates.`__)
|
||||
|
||||
__ https://mail.python.org/pipermail/python-ideas/2014-February/025702.html
|
||||
>>> (expr except Exception as e: e)
|
||||
|
||||
An examination of the Python standard library shows that, while the use
|
||||
of 'as' is fairly common (occurring in roughly one except clause in five),
|
||||
|
@ -732,6 +698,7 @@ Rejected sub-proposals
|
|||
|
||||
finally clause
|
||||
--------------
|
||||
|
||||
The statement form try... finally or try... except... finally has no
|
||||
logical corresponding expression form. Therefore the finally keyword
|
||||
is not a part of this proposal, in any way.
|
||||
|
@ -813,6 +780,10 @@ course, be parenthesized if desired, as can the default::
|
|||
|
||||
value = (expr) except ExceptionType: (default)
|
||||
|
||||
As the entire expression is now required to be in parentheses (which had not
|
||||
been decided at the time when this was debated), there is less need to
|
||||
delineate this section, and in many cases it would be redundant.
|
||||
|
||||
|
||||
Short-hand for "except: pass"
|
||||
-----------------------------
|
||||
|
|
Loading…
Reference in New Issue