[Python-Dev] PEP 463: Exception-catching expressions

Steven D'Aprano steve at pearwood.info
Sat Feb 22 06:01:16 CET 2014


On Fri, Feb 21, 2014 at 02:04:45PM -0500, Yury Selivanov wrote:

> Inconvenience of dict[] raising KeyError was solved by
> introducing the dict.get() method. And I think that
> 
> dct.get('a', 'b')
> 
> is 1000 times better than
> 
> dct['a'] except KeyError: 'b'

I don't think it is better. I think that if we had good 
exception-catching syntax, we wouldn't need a get method.

"get" methods do not solve the problem, they are shift it.

Every class that defines [] look-ups also needs a get method. We can 
write dict.get(key, default), but we can't write list.get(index, 
default). If we "fix" list, we then find that tuple.get(index, default) 
fails. So we "fix" tuple, and then discover that str.get(index, default) 
fails.

If Python were Ruby, then all sequences could inherit from 
one Sequence class that defines:

    def get(self, index, default=None):
        try:
            return self[index]
        except IndexError:
            return default


and similarly for mappings, with KeyError instead of IndexError. But 
this is Python, not Ruby, and there is no requirement that sequences 
must inherit from the same parent class. They just have to provide the 
same duck-typed interface.

Having added a get method to every mapping and sequence, you then call 
list.pop() and discover that it too needs a default. And so on, forever. 
We're forever chasing the next method and function, adding default 
parameters to everything in sight. The latest example is max and min.

Then there are functions or methods which come in pairs, like str.find 
and str.index. From time to time people ask for a version of list.index 
that returns a default value instead of raising. Should we give them 
one? I don't think Python is a better language by adding complexity into 
the libraries and built-ins in this way.

(And let's not forget that while methods and functions can have default 
parameters, expressions cannot. So there is a whole class of potential 
operations that can never be given a default, because they aren't a 
function call.)

The essence of programming is combining primitive constructs to make 
more complicated ones, not in the proliferation of special-purpose 
methods or functions. "get" is less primitive than normal [] lookup, and 
it exists for a special purpose: to avoid needing to break apart what 
ought is a single conceptual expression into a multi-line statement. 
Consider a single conceptual chunk of code, the expression 
process(mydict[key]). In the past, if you wanted to use a default value 
if the key didn't exist, the only alternative was to break that 
expression up into two statements, regardless of whether you used LBYL 
or EAFP idioms:

if key in mydict:
    tmp = mydict[key]
else:
    tmp = default
result = process(tmp)


try:
    tmp = mydict[key]
except KeyError:
    tmp = default
result = process(tmp)


Consequently, to express a single expression *as a single expression*, 
we needed an expression that can do the same thing, and in the past that 
had to be a method:

result = process(mydict.get(key, default))


But now we have a LBYL expression form:

result = process(mydict[key] if key in mydict else default)


and are suggesting a EAFP form:

result = process(mydict[key] except KeyError: default)


If Python had this syntax 20 years ago, would dicts have grown a get 
method? I doubt it very much. People would be told to wrap it in an 
except expression.


> >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
> 
> I'm sorry, it took me a minute to understand what your
> example is doing.  I would rather see two try..except blocks
> than this.

It helps to break it up more appropriately. (Also less cryptic names.) I 
can see PEP 8 will need some changes if this syntax is approved.

    for key in sequence:
        x = (cache1[key] except KeyError: 
             (cache2[key] except KeyError:
              f(key)))
        # Do something with x

This proposal is not about saving lines of code, and if examples are 
written jammed into a single line or two, and that hurts readability, 
they should be formatted better. It is about expressing the EAFP idiom 
in an expression, without giving up the useful property that the 
"except" clause is only evaluated when needed, not in advance.

(Chris, I think that ought to go in the motivation section of the PEP.)



-- 
Steven



More information about the Python-Dev mailing list