[Python-ideas] exception based conditional expression, similar to if-else conditional expression
steve at pearwood.info
Thu Aug 20 16:25:51 CEST 2009
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):
> 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
> 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
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()
>>> 'evaluate' in y.__dict__
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
> 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:
if you want to catch everything except KeyboardInterrupt and one or two
other things, or:
for those rare times you actually do want to catch everything.
More information about the Python-ideas