[Python-ideas] exception based conditional expression, similar to if-else conditional expression

Steven D'Aprano 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):
>         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



More information about the Python-ideas mailing list