
On Thu, 20 Aug 2009 09:07:54 am Jeff McAninch wrote:
I would like to propose an expression, similar to the if-else expression, that responds to exceptions. ... _*Simple Example - type coercion in a list comprehension: *_Current approach: def safe_float(string): try: x = float(string) except ValueError: x = float('nan') return x ... xs = (safe(float(string)) for string in strings)
Personally, I think the above is likely to be the approach in a serious application. For lightweight applications, an expression-based solution would be acceptable, but it has the disadvantage of not being able to be documented or tested independently.
Proposed solution using exception-based conditional expression: xs = ((float(string) except ValueError: float('nan')) for string in strings)
Others have suggested that the colon should be dropped. I dislike that idea, because there's nothing but whitespace delimiting the list of exceptions from the except-expression: EXPR except EXCEPT-LIST EXCEPT-EXPR As far as I can tell, that would be unprecedented in Python syntax. ...
Here is a slightly more involved example than the examples presented above. In data processing, I often string together a sequence of iterable list comprehensions, corresponding to a sequence of operations on a given dataset "ys" to produce a processed dataset "x": xs = (operation_A(x) for x in ys) xs = (operation_B(x) for x in xs if filter_B(x)) xs = (operation_C(x) if (some_condition(x)) else operation_D(x) for x in xs if filter_C(x)) # final, explicit list of values xs = [ x for x in xs ]
Ouch! That last one is better written as: xs = list(xs)
This is often a flexible way for me to define processing and filtering sequences which also seems to have good performance on very large datasets.
Chaining iterators together is a powerful technique, and you don't have to be limited to generator expressions, you can use regular generators too: def operate(xs): for x in xs: try: yield operation_C(x) except ValueError: yield operation_D(x) xs = (operation_A(x) for x in ys) xs = (operation_B(x) for x in xs if filter_B(x)) xs = operate(xs) # finally convert to a list xs = list(xs)
One advantage is that I can quickly mix-and-match from existing processes like this to make a new process. An exception-based conditional would go nicely into many of these process sequences, keeping them both robust and flexible. xs = (operation_N(x) except exceptionN: operation_Nprime(x) for x in xs)
So far this is an argument for iterator-based processing, not for exception-based conditionals :) You can use exceptions in generator exceptions, you just have to put them into a function first.
I also often have object classes which have some common method or attribute. For instance, some of my objects have scope-dependent values: x = y.evaluate(scope)) where scope is typically locals(), globals(), or some other dictionary-like container. But, to keep my code modular, I want to handle, in the same lines of code, objects which do not have some particular method, which leads me to lines of code like: x = y.evaluate(locals()) if ('evaluate' in y.__dict__) else y
Is that real code? I don't think it works the way you think. Given a reasonable definition for y (an instance of a class with no special tricks) the if test will never succeed:
class MyObject(object): ... def evaluate(self, arg): ... return arg ... y = MyObject() y.evaluate(42) 42 'evaluate' in y.__dict__ False
A better way of writing that would be: x = y.evaluate(locals()) if hasattr(y, 'evaluate') else y which have the bonus of working correctly even if y gets its evaluate() method via inheritance. Another way, two lines instead of one: try: x = y.evaluate(locals()) except AttributeError: x = y A third method: make sure all objects you pass have an evaluate() method, even if they are a null-operation that just return self.
This seems not very "Pythonic", similar to using type-testing instead of try-except.
You're talking about the Look Before You Leap technique rather than Easier To Ask Forgiveness Than Permission. Both techniques are valid, and both have advantages. ETAFTP is advantageous when exceptions are rare and hard to predict; LBYL is better if exceptions are common and easy to predict.
In the thread of my previous post to py-dev, there were comments, questions, and suggestions concerning the details of the syntax. Having reflected on this for a couple weeks, I am now most strongly supportive of what is essentially just an inline compression of the current try-except syntax. So the following examples would be allowed: x = expression0 except: default_expression
I would argue strongly that allowing a bare except is a SERIOUS mistake. Bare excepts have very few legitimate uses, and now that string exceptions are deprecated and very rare, it's no hardship to write: except Exception: if you want to catch everything except KeyboardInterrupt and one or two other things, or: except BaseException: for those rare times you actually do want to catch everything. -- Steven D'Aprano