[Python-checkins] peps: PEP 463: More updates by Chris Angelico

yury.selivanov python-checkins at python.org
Sat Feb 22 05:27:56 CET 2014


http://hg.python.org/peps/rev/c52a2ae3d98e
changeset:   5381:c52a2ae3d98e
user:        Yury Selivanov <yselivanov at sprymix.com>
date:        Fri Feb 21 23:27:51 2014 -0500
summary:
  PEP 463: More updates by Chris Angelico

files:
  pep-0463.txt |  197 ++++++++++++++++----------------------
  1 files changed, 84 insertions(+), 113 deletions(-)


diff --git a/pep-0463.txt b/pep-0463.txt
--- a/pep-0463.txt
+++ b/pep-0463.txt
@@ -41,9 +41,6 @@
 * 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 @@
 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 @@
 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 @@
 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 @@
 
 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 @@
 
 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 @@
 
 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 @@
 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 @@
 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 @@
 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 @@
 
 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 @@
 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 @@
 
 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 @@
 
     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 @@
 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 @@
         (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 @@
 
 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 @@
 
     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"
 -----------------------------

-- 
Repository URL: http://hg.python.org/peps


More information about the Python-checkins mailing list