On Fri, Feb 21, 2014 at 6:39 AM, Brett Cannon <brett at python.org> wrote: > > > > On Fri, Feb 21, 2014 at 9:03 AM, Eli Bendersky <eliben at gmail.com> wrote: > >> On Thu, Feb 20, 2014 at 7:15 PM, Chris Angelico <rosuav at gmail.com> wrote: >> >>> PEP: 463 >>> Title: Exception-catching expressions >>> Version: $Revision$ >>> Last-Modified: $Date$ >>> Author: Chris Angelico <rosuav at gmail.com> >>> Status: Draft >>> Type: Standards Track >>> Content-Type: text/x-rst >>> Created: 15-Feb-2014 >>> Python-Version: 3.5 >>> Post-History: 16-Feb-2014, 21-Feb-2014 >>> >>> >>> Abstract >>> ======== >>> >>> Just as PEP 308 introduced a means of value-based conditions in an >>> expression, this system allows exception-based conditions to be used >>> as part of an expression. >>> >> >> >> Chris, while I also commend you for the comprehensive PEP, I'm -1 on the >> proposal, for two main reasons: >> >> 1. Many proposals suggest new syntax to gain some succinctness. Each has >> to be judged for its own merits, and in this case IMHO the cons eclipse the >> pros. I don't think this will save a lot of code in a typical >> well-structured program - maybe a few lines out of hundreds. On the other >> hand, it adds yet another syntax to remember and understand, which is not >> the Pythonic way. >> >> 2. Worse, this idea subverts exceptions to control flow, which is not >> only un-Pythonic but also against the accepted practices of programming in >> general. Here, the comparison to PEP 308 is misguided. PEP 308, whatever >> syntax it adds, still remains within the domain of normal control flow. PEP >> 463, OTOH, makes it deliberately easy to make exceptions part of >> non-exceptional code, encouraging very bad programming practices. >> >> > But we subsumed using exception for control flow long ago. > StopIteration is the most blatant, but when we added dict.get(), getattr() > with a default value, iter() with its default, etc. we made it very clear > that while exceptions typically represent exceptional situations, they can > also be used as an easy signal of something which we expect you to handle > and easily recover from (and returning None or some other singleton doesn't > make sense), and yet without explicit API support it takes a heavy-handed > statement to deal with. > > True, but at least you still have to explicitly try...except... which takes a toll on the code so isn't taken lightly. Adding except into expressions, I fear, will proliferate this usage much more. Eli > -Brett > > > >> Eli >> >> >> >> >>> >>> >>> Motivation >>> ========== >>> >>> A number of functions and methods have parameters which will cause >>> them to return a specified value instead of raising an exception. The >>> current system is ad-hoc and inconsistent, and requires that each >>> function be individually written to have this functionality; not all >>> support this. >>> >>> * dict.get(key, default) - second positional argument in place of >>> KeyError >>> >>> * next(iter, default) - second positional argument in place of >>> StopIteration >>> >>> * list.pop() - no way to return a default >>> >>> * seq[index] - no way to handle a bounds error >>> >>> * 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 >>> >>> >>> Rationale >>> ========= >>> >>> The current system requires that a function author predict the need >>> for a default, and implement support for it. If this is not done, a >>> full try/except block is needed. >>> >>> Since try/except is a statement, it is impossible to catch exceptions >>> in the middle of an expression. Just as if/else does for conditionals >>> and lambda does for function definitions, so does this allow exception >>> catching in an expression context. >>> >>> 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: >>> return default() >>> value = except_(lambda: 1/x, ZeroDivisionError, lambda: float("nan")) >>> >>> which is clunky, and unable to handle multiple exception clauses; or >>> eval:: >>> >>> def except_(expression, exception_list, default): >>> try: >>> return eval(expression, globals_of_caller(), >>> locals_of_caller()) >>> except exception_list as exc: >>> l = locals_of_caller().copy() >>> l['exc'] = exc >>> return eval(default, globals_of_caller(), l) >>> >>> def globals_of_caller(): >>> return sys._getframe(2).f_globals >>> >>> def locals_of_caller(): >>> return sys._getframe(2).f_locals >>> >>> value = except_("""1/x""",ZeroDivisionError,""" "Can't divide by >>> zero" """) >>> >>> which is even clunkier, and relies on implementation-dependent hacks. >>> (Writing globals_of_caller() and locals_of_caller() for interpreters >>> other than CPython is left as an exercise for the reader.) >>> >>> Raymond Hettinger `expresses`__ a desire for such a consistent >>> API. Something similar has been `requested`__ `multiple`__ `times`__ >>> in the past. >>> >>> __ >>> https://mail.python.org/pipermail/python-ideas/2014-February/025443.html >>> __ https://mail.python.org/pipermail/python-ideas/2013-March/019760.html >>> __ >>> https://mail.python.org/pipermail/python-ideas/2009-August/005441.html >>> __ >>> https://mail.python.org/pipermail/python-ideas/2008-August/001801.html >>> >>> >>> Proposal >>> ======== >>> >>> Just as the 'or' operator and the three part 'if-else' expression give >>> short circuiting methods of catching a falsy value and replacing it, >>> this syntax gives a short-circuiting method of catching an exception >>> and replacing it. >>> >>> This currently works:: >>> >>> lst = [1, 2, None, 3] >>> value = lst[2] or "No value" >>> >>> The proposal adds this:: >>> >>> lst = [1, 2] >>> value = lst[2] except IndexError: "No value" >>> >>> Specifically, the syntax proposed is:: >>> >>> 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 >>> 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. >>> >>> 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 >>> >>> 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):: >>> >>> # 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 >>> ) >>> >>> Using parentheses to force an alternative interpretation works as >>> expected:: >>> >>> # Will catch an Exception2 thrown by either expr or default1 >>> value = ( >>> (expr except Exception1: default1) >>> except Exception2: default2 >>> ) >>> # Will catch an Exception2 thrown by default1 only >>> value = (expr except Exception1: >>> (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. >>> >>> >>> 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.) >>> >>> 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 >>> ------------------------------------- >>> >>> Should it be legal to parenthesize the except clauses, separately from >>> the expression that could raise? Example:: >>> >>> value = expr ( >>> except Exception1 [as e]: default1 >>> except Exception2 [as e]: default2 >>> # ... 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) >>> >>> 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) >>> >>> >>> Short-hand for "except: pass" >>> ----------------------------- >>> >>> The following was been suggested as a similar >>> short-hand, though not technically an expression:: >>> >>> statement except Exception: pass >>> >>> try: >>> statement >>> except Exception: >>> pass >>> >>> For instance, a common use-case is attempting the removal of a file:: >>> os.unlink(some_file) except OSError: pass >>> >>> There is an equivalent already in Python 3.4, however, in contextlib:: >>> from contextlib import suppress >>> with suppress(OSError): os.unlink(some_file) >>> >>> 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 >>> ========= >>> >>> This document has been placed in the public domain. >>> >>> >>> .. >>> Local Variables: >>> mode: indented-text >>> indent-tabs-mode: nil >>> sentence-end-double-space: t >>> fill-column: 70 >>> coding: utf-8 >>> End: >>> _______________________________________________ >>> Python-Dev mailing list >>> Python-Dev at python.org >>> https://mail.python.org/mailman/listinfo/python-dev >>> Unsubscribe: >>> https://mail.python.org/mailman/options/python-dev/eliben%40gmail.com >>> >> >> >> _______________________________________________ >> Python-Dev mailing list >> Python-Dev at python.org >> https://mail.python.org/mailman/listinfo/python-dev >> Unsubscribe: >> https://mail.python.org/mailman/options/python-dev/brett%40python.org >> >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.python.org/pipermail/python-dev/attachments/20140221/5a57f563/attachment-0001.html>
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4