PEP 463 - add latest changes by Chris Angelico.
This commit is contained in:
parent
b1d315a541
commit
953fd850a8
776
pep-0463.txt
776
pep-0463.txt
|
@ -46,12 +46,6 @@ support this.
|
|||
|
||||
* statistics.mean(data) - no way to handle an empty iterator
|
||||
|
||||
Additionally, this syntax would 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
|
||||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
@ -69,15 +63,24 @@ This provides a clean and consistent way for a function to provide a
|
|||
default: it simply raises an appropriate exception, and the caller
|
||||
catches it.
|
||||
|
||||
With some situations, an LBYL technique can be used (checking if some
|
||||
sequence has enough length before indexing into it, for instance). This is
|
||||
not safe in all cases, but as it is often convenient, programmers will be
|
||||
tempted to sacrifice the safety of EAFP in favour of the notational brevity
|
||||
of LBYL. Additionally, some LBYL techniques (eg involving getattr with
|
||||
three arguments) warp the code into looking like literal strings rather
|
||||
than attribute lookup, which can impact readability. A convenient EAFP
|
||||
notation solves all of this.
|
||||
|
||||
There's no convenient way to write a helper function to do this; the
|
||||
nearest is something ugly using either lambda::
|
||||
|
||||
def except_(expression, exception_list, default):
|
||||
try:
|
||||
return expression()
|
||||
except exception_list as e:
|
||||
return default(e)
|
||||
value = except_(lambda: 1/x, ZeroDivisionError, lambda e: float("nan"))
|
||||
except exception_list:
|
||||
return default()
|
||||
value = except_(lambda: 1/x, ZeroDivisionError, lambda: float("nan"))
|
||||
|
||||
which is clunky, and unable to handle multiple exception clauses; or
|
||||
eval::
|
||||
|
@ -132,44 +135,441 @@ The proposal adds this::
|
|||
|
||||
Specifically, the syntax proposed is::
|
||||
|
||||
expr except exc [as e]: default [except exc2 [as e]: default2] ...
|
||||
expr except exception_list: default
|
||||
|
||||
where expr, exc, and default are all expressions. First, expr is
|
||||
evaluated. If no exception is raised, its value is the value of the
|
||||
overall expression. If any exception is raised, exc is evaluated, and
|
||||
should result in either a type or a tuple, just as with the statement
|
||||
form of try/except. Any matching exception will result in the
|
||||
corresponding default expression being evaluated and becoming the
|
||||
value of the expression. As with the statement form of try/ except,
|
||||
subsequent except clauses will be checked if the first one does not
|
||||
match, and if none match, the raised exception will propagate upward.
|
||||
Also as with the try/except statement, the keyword 'as' can be used to
|
||||
bind the exception object to a local name.
|
||||
where expr, exception_list, and default are all expressions. First,
|
||||
expr is evaluated. If no exception is raised, its value is the value
|
||||
of the overall expression. If any exception is raised, exception_list
|
||||
is evaluated, and should result in either a type or a tuple, just as
|
||||
with the statement form of try/except. Any matching exception will
|
||||
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.
|
||||
|
||||
Omitting the exception list should be legal, just as with the
|
||||
statement form of try/except, though this should of course be
|
||||
discouraged by PEP 8.
|
||||
|
||||
The exception object can be captured just as in a normal try/except
|
||||
block::
|
||||
|
||||
# Return the next yielded or returned value from a generator
|
||||
value = next(it) except StopIteration as e: e.args[0]
|
||||
|
||||
This is effectively equivalent to::
|
||||
|
||||
try:
|
||||
_ = next(it)
|
||||
except StopIteration as e:
|
||||
_ = e.args[0]
|
||||
value = _
|
||||
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.)
|
||||
|
||||
This ternary operator would be between lambda and if/else in
|
||||
precedence.
|
||||
|
||||
Consider this example of a two-level cache::
|
||||
for key in sequence:
|
||||
x = (lvl1[key] except KeyError: (lvl2[key] except KeyError: f(key)))
|
||||
# do something with x
|
||||
|
||||
Chaining
|
||||
--------
|
||||
This cannot be rewritten as::
|
||||
x = lvl1.get(key, lvl2.get(key, f(key)))
|
||||
|
||||
which, despite being shorter, defeats the purpose of the cache, as it must
|
||||
calculate a default value to pass to get(). The .get() version calculates
|
||||
backwards; the exception-testing version calculates forwards, as would be
|
||||
expected. The nearest useful equivalent would be::
|
||||
x = lvl1.get(key) or lvl2.get(key) or f(key)
|
||||
which depends on the values being nonzero, as well as depending on the cache
|
||||
object supporting this functionality.
|
||||
|
||||
|
||||
Alternative Proposals
|
||||
=====================
|
||||
|
||||
Discussion on python-ideas brought up the following syntax suggestions::
|
||||
|
||||
value = expr except default if Exception [as e]
|
||||
value = expr except default for Exception [as e]
|
||||
value = expr except default from Exception [as e]
|
||||
value = expr except Exception [as e] return default
|
||||
value = expr except (Exception [as e]: default)
|
||||
value = expr except Exception [as e] try default
|
||||
value = expr except Exception [as e] continue with default
|
||||
value = default except Exception [as e] else expr
|
||||
value = try expr except Exception [as e]: default
|
||||
value = expr except default # Catches anything
|
||||
value = expr except(Exception) default # Catches only the named type(s)
|
||||
value = default if expr raise Exception
|
||||
value = expr or else default if Exception
|
||||
value = expr except Exception [as e] -> default
|
||||
value = expr except Exception [as e] pass default
|
||||
|
||||
It has also been suggested that a new keyword be created, rather than
|
||||
reusing an existing one. Such proposals fall into the same structure
|
||||
as the last form, but with a different keyword in place of 'pass'.
|
||||
Suggestions include 'then', 'when', and 'use'. Also, in the context of
|
||||
the "default if expr raise Exception" proposal, it was suggested that a
|
||||
new keyword "raises" be used.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Open Issues
|
||||
===========
|
||||
|
||||
Parentheses around the entire expression
|
||||
----------------------------------------
|
||||
|
||||
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.`__
|
||||
|
||||
__ https://mail.python.org/pipermail/python-ideas/2014-February/025647.html
|
||||
|
||||
|
||||
Example usage
|
||||
=============
|
||||
|
||||
For each example, an approximately-equivalent statement form is given,
|
||||
to show how the expression will be parsed. These are not always
|
||||
strictly equivalent, but will accomplish the same purpose. It is NOT
|
||||
safe for the interpreter to translate one into the other.
|
||||
|
||||
A number of these examples are taken directly from the Python standard
|
||||
library, with file names and line numbers correct as of early Feb 2014.
|
||||
Many of these patterns are extremely common.
|
||||
|
||||
Retrieve an argument, defaulting to None::
|
||||
cond = args[1] except IndexError: None
|
||||
|
||||
# Lib/pdb.py:803:
|
||||
try:
|
||||
cond = args[1]
|
||||
except IndexError:
|
||||
cond = None
|
||||
|
||||
Fetch information from the system if available::
|
||||
pwd = os.getcwd() except OSError: None
|
||||
|
||||
# Lib/tkinter/filedialog.py:210:
|
||||
try:
|
||||
pwd = os.getcwd()
|
||||
except OSError:
|
||||
pwd = None
|
||||
|
||||
Attempt a translation, falling back on the original::
|
||||
e.widget = self._nametowidget(W) except KeyError: W
|
||||
|
||||
# Lib/tkinter/__init__.py:1222:
|
||||
try:
|
||||
e.widget = self._nametowidget(W)
|
||||
except KeyError:
|
||||
e.widget = W
|
||||
|
||||
Read from an iterator, continuing with blank lines once it's
|
||||
exhausted::
|
||||
line = readline() except StopIteration: ''
|
||||
|
||||
# Lib/lib2to3/pgen2/tokenize.py:370:
|
||||
try:
|
||||
line = readline()
|
||||
except StopIteration:
|
||||
line = ''
|
||||
|
||||
Retrieve platform-specific information (note the DRY improvement);
|
||||
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: ''
|
||||
|
||||
# Lib/sysconfig.py:529:
|
||||
try:
|
||||
_CONFIG_VARS['abiflags'] = sys.abiflags
|
||||
except AttributeError:
|
||||
# sys.abiflags may not be defined on all platforms.
|
||||
_CONFIG_VARS['abiflags'] = ''
|
||||
|
||||
Retrieve an indexed item, defaulting to None (similar to dict.get)::
|
||||
def getNamedItem(self, name):
|
||||
return self._attrs[name] except KeyError: None
|
||||
|
||||
# Lib/xml/dom/minidom.py:573:
|
||||
def getNamedItem(self, name):
|
||||
try:
|
||||
return self._attrs[name]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
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
|
||||
|
||||
# Lib/tarfile.py:2198:
|
||||
try:
|
||||
g = grp.getgrnam(tarinfo.gname)[2]
|
||||
except KeyError:
|
||||
g = tarinfo.gid
|
||||
try:
|
||||
u = pwd.getpwnam(tarinfo.uname)[2]
|
||||
except KeyError:
|
||||
u = tarinfo.uid
|
||||
|
||||
Perform some lengthy calculations in EAFP mode, handling division by
|
||||
zero as a sort of sticky NaN::
|
||||
|
||||
value = calculate(x) except ZeroDivisionError: float("nan")
|
||||
|
||||
try:
|
||||
value = calculate(x)
|
||||
except ZeroDivisionError:
|
||||
value = float("nan")
|
||||
|
||||
Calculate the mean of a series of numbers, falling back on zero::
|
||||
|
||||
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()
|
||||
|
||||
try:
|
||||
(overrides[x] or default).ping()
|
||||
except IndexError:
|
||||
default.ping()
|
||||
|
||||
|
||||
Narrowing of exception-catching scope
|
||||
-------------------------------------
|
||||
|
||||
The following examples, taken directly from Python's standard library,
|
||||
demonstrate how the scope of the try/except can be conveniently narrowed.
|
||||
To do this with the statement form of try/except would require a temporary
|
||||
variable, but it's far cleaner as an expression.
|
||||
|
||||
Lib/ipaddress.py:343::
|
||||
try:
|
||||
ips.append(ip.ip)
|
||||
except AttributeError:
|
||||
ips.append(ip.network_address)
|
||||
Becomes::
|
||||
ips.append(ip.ip except AttributeError: ip.network_address)
|
||||
The expression form is nearly equivalent to this::
|
||||
try:
|
||||
_ = ip.ip
|
||||
except AttributeError:
|
||||
_ = ip.network_address
|
||||
ips.append(_)
|
||||
|
||||
Lib/tempfile.py:130::
|
||||
try:
|
||||
dirlist.append(_os.getcwd())
|
||||
except (AttributeError, OSError):
|
||||
dirlist.append(_os.curdir)
|
||||
Becomes::
|
||||
dirlist.append(_os.getcwd() except (AttributeError, OSError): _os.curdir)
|
||||
|
||||
Lib/asyncore.py:264::
|
||||
try:
|
||||
status.append('%s:%d' % self.addr)
|
||||
except TypeError:
|
||||
status.append(repr(self.addr))
|
||||
Becomes::
|
||||
status.append('%s:%d' % self.addr except TypeError: repr(self.addr))
|
||||
|
||||
|
||||
Comparisons with other languages
|
||||
================================
|
||||
|
||||
(With thanks to Andrew Barnert for compiling this section.)
|
||||
|
||||
`Ruby's`__ "begin…rescue…rescue…else…ensure…end" is an expression
|
||||
(potentially with statements inside it). It has the equivalent of an "as"
|
||||
clause, and the equivalent of bare except. And it uses no punctuation or
|
||||
keyword between the bare except/exception class/exception class with as
|
||||
clause and the value. (And yes, it's ambiguous unless you understand
|
||||
Ruby's statement/expression rules.)
|
||||
|
||||
__ http://www.skorks.com/2009/09/ruby-exceptions-and-exception-handling/
|
||||
|
||||
::
|
||||
|
||||
x = begin computation() rescue MyException => e default(e) end;
|
||||
x = begin computation() rescue MyException default() end;
|
||||
x = begin computation() rescue default() end;
|
||||
x = begin computation() rescue MyException default() rescue OtherException other() end;
|
||||
|
||||
In terms of this PEP::
|
||||
|
||||
x = computation() except MyException as e default(e)
|
||||
x = computation() except MyException default(e)
|
||||
x = computation() except default(e)
|
||||
x = computation() except MyException default() except OtherException other()
|
||||
|
||||
`Erlang`__ has a try expression that looks like this::
|
||||
|
||||
__ http://erlang.org/doc/reference_manual/expressions.html#id79284
|
||||
|
||||
x = try computation() catch MyException:e -> default(e) end;
|
||||
x = try computation() catch MyException:e -> default(e); OtherException:e -> other(e) end;
|
||||
|
||||
The class and "as" name are mandatory, but you can use "_" for either.
|
||||
There's also an optional "when" guard on each, and a "throw" clause that
|
||||
you can catch, which I won't get into. To handle multiple exceptions,
|
||||
you just separate the clauses with semicolons, which I guess would map
|
||||
to commas in Python. So::
|
||||
|
||||
x = try computation() except MyException as e -> default(e)
|
||||
x = try computation() except MyException as e -> default(e), OtherException as e->other_default(e)
|
||||
|
||||
Erlang also has a "catch" expression, which, despite using the same keyword,
|
||||
is completely different, and you don't want to know about it.
|
||||
|
||||
|
||||
The ML family has two different ways of dealing with this, "handle" and
|
||||
"try"; the difference between the two is that "try" pattern-matches the
|
||||
exception, which gives you the effect of multiple except clauses and as
|
||||
clauses. In either form, the handler clause is punctuated by "=>" in
|
||||
some dialects, "->" in others.
|
||||
|
||||
To avoid confusion, I'll write the function calls in Python style.
|
||||
|
||||
Here's `SML's`__ "handle"::
|
||||
__ http://www.cs.cmu.edu/~rwh/introsml/core/exceptions.htm
|
||||
|
||||
let x = computation() handle MyException => default();;
|
||||
|
||||
Here's `OCaml's`__ "try"::
|
||||
__ http://www2.lib.uchicago.edu/keith/ocaml-class/exceptions.html
|
||||
|
||||
let x = try computation() with MyException explanation -> default(explanation);;
|
||||
|
||||
let x = try computation() with
|
||||
|
||||
MyException(e) -> default(e)
|
||||
| MyOtherException() -> other_default()
|
||||
| (e) -> fallback(e);;
|
||||
|
||||
In terms of this PEP, these would be something like::
|
||||
|
||||
x = computation() except MyException => default()
|
||||
x = try computation() except MyException e -> default()
|
||||
x = (try computation()
|
||||
except MyException as e -> default(e)
|
||||
except MyOtherException -> other_default()
|
||||
except BaseException as e -> fallback(e))
|
||||
|
||||
Many ML-inspired but not-directly-related languages from academia mix things
|
||||
up, usually using more keywords and fewer symbols. So, the `Oz`__ would map
|
||||
to Python as::
|
||||
__ http://mozart.github.io/mozart-v1/doc-1.4.0/tutorial/node5.html
|
||||
|
||||
x = try computation() catch MyException as e then default(e)
|
||||
|
||||
|
||||
Many Lisp-derived languages, like `Clojure,`__ implement try/catch as special
|
||||
forms (if you don't know what that means, think function-like macros), so you
|
||||
write, effectively::
|
||||
__ http://clojure.org/special_forms#Special%20Forms--(try%20expr*%20catch-clause*%20finally-clause?)
|
||||
|
||||
try(computation(), catch(MyException, explanation, default(explanation)))
|
||||
|
||||
try(computation(),
|
||||
catch(MyException, explanation, default(explanation)),
|
||||
catch(MyOtherException, explanation, other_default(explanation)))
|
||||
|
||||
In Common Lisp, this is done with a slightly clunkier `"handler-case" macro,`__
|
||||
but the basic idea is the same.
|
||||
|
||||
__ http://clhs.lisp.se/Body/m_hand_1.htm
|
||||
|
||||
|
||||
The Lisp style is, surprisingly, used by some languages that don't have
|
||||
macros, like Lua, where `xpcall`__ takes functions. Writing lambdas
|
||||
Python-style instead of Lua-style::
|
||||
__ http://www.gammon.com.au/scripts/doc.php?lua=xpcall
|
||||
|
||||
x = xpcall(lambda: expression(), lambda e: default(e))
|
||||
|
||||
This actually returns (true, expression()) or (false, default(e)), but I think we can ignore that part.
|
||||
|
||||
|
||||
Haskell is actually similar to Lua here (except that it's all done
|
||||
with monads, of course)::
|
||||
|
||||
x = do catch(lambda: expression(), lambda e: default(e))
|
||||
|
||||
You can write a pattern matching expression within the function to decide
|
||||
what to do with it; catching and re-raising exceptions you don't want is
|
||||
cheap enough to be idiomatic.
|
||||
|
||||
But Haskell infixing makes this nicer::
|
||||
|
||||
x = do expression() `catch` lambda: default()
|
||||
x = do expression() `catch` lambda e: default(e)
|
||||
|
||||
And that makes the parallel between the lambda colon and the except
|
||||
colon in the proposal much more obvious::
|
||||
|
||||
|
||||
x = expression() except Exception: default()
|
||||
x = expression() except Exception as e: default(e)
|
||||
|
||||
|
||||
`Tcl`__ has the other half of Lua's xpcall; catch is a function which returns
|
||||
true if an exception was caught, false otherwise, and you get the value out
|
||||
in other ways. And it's all built around the the implicit quote-and-exec
|
||||
that everything in Tcl is based on, making it even harder to describe in
|
||||
Python terms than Lisp macros, but something like::
|
||||
__ http://wiki.tcl.tk/902
|
||||
|
||||
if {[ catch("computation()") "explanation"]} { default(explanation) }
|
||||
|
||||
|
||||
`Smalltalk`__ is also somewhat hard to map to Python. The basic version
|
||||
would be::
|
||||
__ http://smalltalk.gnu.org/wiki/exceptions
|
||||
|
||||
x := computation() on:MyException do:default()
|
||||
|
||||
… but that's basically Smalltalk's passing-arguments-with-colons
|
||||
syntax, not its exception-handling syntax.
|
||||
|
||||
|
||||
Deferred sub-proposals
|
||||
======================
|
||||
|
||||
Multiple except clauses
|
||||
-----------------------
|
||||
|
||||
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)::
|
||||
|
@ -200,47 +600,8 @@ 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.
|
||||
|
||||
|
||||
Alternative Proposals
|
||||
=====================
|
||||
|
||||
Discussion on python-ideas brought up the following syntax suggestions::
|
||||
|
||||
value = expr except default if Exception [as e]
|
||||
value = expr except default for Exception [as e]
|
||||
value = expr except default from Exception [as e]
|
||||
value = expr except Exception [as e] return default
|
||||
value = expr except (Exception [as e]: default)
|
||||
value = expr except Exception [as e] try default
|
||||
value = expr except Exception [as e] continue with default
|
||||
value = default except Exception [as e] else expr
|
||||
value = try expr except Exception [as e]: default
|
||||
value = expr except Exception [as e] pass default
|
||||
|
||||
It has also been suggested that a new keyword be created, rather than
|
||||
reusing an existing one. Such proposals fall into the same structure
|
||||
as the last form, but with a different keyword in place of 'pass'.
|
||||
Suggestions include 'then', 'when', and 'use'.
|
||||
|
||||
Additionally, the following has been suggested as a similar
|
||||
short-hand, though not technically an expression::
|
||||
|
||||
statement except Exception: pass
|
||||
|
||||
try:
|
||||
statement
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
Open Issues
|
||||
===========
|
||||
|
||||
Commas between multiple except clauses
|
||||
--------------------------------------
|
||||
|
||||
Where there are multiple except clauses, should they be separated by
|
||||
commas? It may be easier for the parser, that way::
|
||||
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,
|
||||
|
@ -269,16 +630,113 @@ This would prevent the accidental omission of a comma from changing
|
|||
the expression's meaning.
|
||||
|
||||
|
||||
Parentheses around the entire expression
|
||||
----------------------------------------
|
||||
Capturing the exception object
|
||||
------------------------------
|
||||
|
||||
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.`__
|
||||
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.)
|
||||
|
||||
__ https://mail.python.org/pipermail/python-ideas/2014-February/025647.html
|
||||
Additionally, this syntax would 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
|
||||
|
||||
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),
|
||||
it is extremely *uncommon* in the cases which could logically be converted
|
||||
into the expression form. Its few uses can simply be left unchanged.
|
||||
Consequently, in the interests of simplicity, the 'as' clause is not
|
||||
included in this proposal. A subsequent Python version can add this without
|
||||
breaking any existing code, as 'as' is already a keyword.
|
||||
|
||||
One example where this could possibly be useful is Lib/imaplib.py:568::
|
||||
try: typ, dat = self._simple_command('LOGOUT')
|
||||
except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]]
|
||||
This could become::
|
||||
typ, dat = (self._simple_command('LOGOUT')
|
||||
except BaseException as e: ('NO', '%s: %s' % (type(e), e)))
|
||||
Or perhaps some other variation. This is hardly the most compelling use-case,
|
||||
but an intelligent look at this code could tidy it up significantly. In the
|
||||
absence of further examples showing any need of the exception object, I have
|
||||
opted to defer indefinitely the recommendation.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Bare except having different meaning
|
||||
------------------------------------
|
||||
|
||||
With several of the proposed syntaxes, omitting the exception type name
|
||||
would be easy and concise, and would be tempting. For convenience's sake,
|
||||
it might be advantageous to have a bare 'except' clause mean something
|
||||
more useful than "except BaseException". Proposals included having it
|
||||
catch Exception, or some specific set of "common exceptions" (subclasses
|
||||
of a new type called ExpressionError), or have it look for a tuple named
|
||||
ExpressionError in the current scope, with a built-in default such as
|
||||
(ValueError, UnicodeError, AttributeError, EOFError, IOError, OSError,
|
||||
LookupError, NameError, ZeroDivisionError). All of these were rejected,
|
||||
for severa reasons.
|
||||
|
||||
* First and foremost, consistency with the statement form of try/except
|
||||
would be broken. Just as a list comprehension or ternary if expression
|
||||
can be explained by "breaking it out" into its vertical statement form,
|
||||
an expression-except should be able to be explained by a relatively
|
||||
mechanical translation into a near-equivalent statement. Any form of
|
||||
syntax common to both should therefore have the same semantics in each,
|
||||
and above all should not have the subtle difference of catching more in
|
||||
one than the other, as it will tend to attract unnoticed bugs.
|
||||
|
||||
* Secondly, the set of appropriate exceptions to catch would itself be
|
||||
a huge point of contention. It would be impossible to predict exactly
|
||||
which exceptions would "make sense" to be caught; why bless some of them
|
||||
with convenient syntax and not others?
|
||||
|
||||
* And finally (this partly because the recommendation was that a bare
|
||||
except should be actively encouraged, once it was reduced to a "reasonable"
|
||||
set of exceptions), any situation where you catch an exception you don't
|
||||
expect to catch is an unnecessary bug magnet.
|
||||
|
||||
Consequently, the use of a bare 'except' is down to two possibilities:
|
||||
either it is syntactically forbidden in the expression form, or it is
|
||||
permitted with the exact same semantics as in the statement form (namely,
|
||||
that it catch BaseException and be unable to capture it with 'as').
|
||||
|
||||
|
||||
Bare except clauses
|
||||
-------------------
|
||||
|
||||
PEP 8 rightly advises against the use of a bare 'except'. While it is
|
||||
syntactically legal in a statement, and for backward compatibility must
|
||||
remain so, there is little value in encouraging its use. In an expression
|
||||
except clause, "except:" is a SyntaxError; use the equivalent long-hand
|
||||
form "except BaseException:" instead. A future version of Python MAY choose
|
||||
to reinstate this, which can be done without breaking compatibility.
|
||||
|
||||
|
||||
Parentheses around the except clauses
|
||||
|
@ -293,130 +751,42 @@ the expression that could raise? Example::
|
|||
# ... except ExceptionN [as e]: defaultN
|
||||
)
|
||||
|
||||
This is more compelling when one or both of the deferred sub-proposals
|
||||
of multiple except clauses and/or exception capturing is included. In
|
||||
their absence, the parentheses would be thus::
|
||||
value = expr except ExceptionType: default
|
||||
value = expr (except ExceptionType: default)
|
||||
|
||||
Scope of default expressions and 'as'
|
||||
-------------------------------------
|
||||
|
||||
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.)
|
||||
|
||||
(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
|
||||
The advantage is minimal, and the potential to confuse a reader into
|
||||
thinking the except clause is separate from the expression, or into thinking
|
||||
this is a function call, makes this non-compelling. The expression can, of
|
||||
course, be parenthesized if desired, as can the default::
|
||||
value = (expr) except ExceptionType: (default)
|
||||
|
||||
|
||||
Example usage
|
||||
=============
|
||||
Short-hand for "except: pass"
|
||||
-----------------------------
|
||||
|
||||
For each example, an approximately-equivalent statement form is given,
|
||||
to show how the expression will be parsed. These are not always
|
||||
strictly equivalent, but will accomplish the same purpose. It is NOT
|
||||
safe for the interpreter to translate one into the other.
|
||||
The following was been suggested as a similar
|
||||
short-hand, though not technically an expression::
|
||||
|
||||
Perform some lengthy calculations in EAFP mode, handling division by
|
||||
zero as a sort of sticky NaN::
|
||||
|
||||
value = calculate(x) except ZeroDivisionError: float("nan")
|
||||
statement except Exception: pass
|
||||
|
||||
try:
|
||||
value = calculate(x)
|
||||
except ZeroDivisionError:
|
||||
value = float("nan")
|
||||
statement
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Retrieving from a generator, either the next yielded value or the
|
||||
returned, and coping with the absence of such a return value::
|
||||
For instance, a common use-case is attempting the removal of a file::
|
||||
os.unlink(some_file) except OSError: pass
|
||||
|
||||
value = (next(it)
|
||||
except StopIteration as e:
|
||||
(e.args[0] except IndexError: None)
|
||||
)
|
||||
There is an equivalent already in Python 3.4, however, in contextlib::
|
||||
from contextlib import suppress
|
||||
with suppress(OSError): os.unlink(some_file)
|
||||
|
||||
try:
|
||||
value = next(it)
|
||||
except StopIteration as e:
|
||||
try:
|
||||
value = e.args[0]
|
||||
except IndexError:
|
||||
value = None
|
||||
|
||||
Calculate the mean of a series of numbers, falling back on zero::
|
||||
|
||||
value = statistics.mean(lst) except statistics.StatisticsError: 0
|
||||
|
||||
try:
|
||||
value = statistics.mean(lst)
|
||||
except statistics.StatisticsError:
|
||||
value = 0
|
||||
|
||||
Set a PyGTK label to a human-readable result from fetching a URL::
|
||||
|
||||
display.set_text(
|
||||
urllib.request.urlopen(url)
|
||||
except urllib.error.HTTPError as e: "Error %d: %s"%(x.getcode(), x.msg)
|
||||
except (ValueError, urllib.error.URLError) as e: "Invalid URL: "+str(e)
|
||||
)
|
||||
|
||||
try:
|
||||
display.set_text(urllib.request.urlopen(url))
|
||||
except urllib.error.HTTPError as e:
|
||||
display.set_text("Error %d: %s"%(x.getcode(), x.msg))
|
||||
except (ValueError, urllib.error.URLError) as e:
|
||||
display.set_text("Invalid URL: "+str(e))
|
||||
|
||||
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: "I'm an idiot and using 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:
|
||||
_ = "I'm an idiot and using 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()
|
||||
|
||||
try:
|
||||
(overrides[x] or default).ping()
|
||||
except IndexError:
|
||||
default.ping()
|
||||
|
||||
|
||||
Rejected sub-proposals
|
||||
======================
|
||||
|
||||
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.
|
||||
As this is already a single line (or two with a break after the colon),
|
||||
there is little need of new syntax and a confusion of statement vs
|
||||
expression to achieve this.
|
||||
|
||||
|
||||
Copyright
|
||||
|
|
Loading…
Reference in New Issue