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

Steven D'Aprano steve at pearwood.info
Thu Mar 6 02:15:38 CET 2014


On Wed, Mar 05, 2014 at 12:57:03PM -0800, Thomas Wouters wrote:
> On Thu, Feb 27, 2014 at 1:29 PM, Chris Angelico <rosuav at gmail.com> wrote:
> 
> > +Had this facility existed early in Python's history, there would have been
> > +no need to create dict.get() and related methods;
> 
> 
> FWIW, after experimenting and some consideration I've come to the
> conclusion that this is incorrect. 'd[k] except KeyError: default' is still
> much broader than dict.get(k):

I don't think your example proves what you think it does. I think it 
demonstrates a bug in the dict.get method. The documentation for get 
states clearly that get will never raise KeyError:

    Return the value for key if key is in the dictionary, else default.
    If default is not given, it defaults to None, so that this method 
    never raises a KeyError.

http://docs.python.org/3/library/stdtypes.html#dict.get


but your example demonstrates that in fact it can raise KeyError 
(albeit under some rather unusual circumstances):


> Python 3.4.0rc1+ (default:aa2ae744e701+, Feb 24 2014, 01:22:15)
> [GCC 4.6.3] on linux
> Type "help", "copyright", "credits" or "license" for more information.
> >>> expensive_calculation = hash
> >>> class C:
> ...     _hash_cache = {}
> ...     def __init__(self, value):
> ...         self.value = value
> ...         if value not in self._hash_cache:
> ...             self._hash_cache[value] = expensive_calculation(value)
> ...     def __hash__(self):
> ...         return self._hash_cache[self.value]
> ...     def __eq__(self, other):
> ...         return self.value == other
> ...
> >>> a, b, c, d = C(1), C(2), C(3), C(4)
> >>> D = {a: 1, b: 2, c: 3, d: 4}
> >>> a.value = 5

> >>> print("except expr:", (D[a] except KeyError: 'default'))
> except expr: default

> >>> print("dict.get:", D.get(a, 'default'))
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
>   File "<stdin>", line 8, in __hash__
> KeyError: 5

According to the documentation, this behaviour is wrong.

Now, you might argue that the documentation is wrong. I'm sympathetic to 
that argument, but *as documented now*, dict.get is documented as being 
logically equivalent to:

try:
    return d[key]
except KeyError:
    return default


The fact that it actually isn't is an artifact of the specific 
implementation used. If it were a deliberate design choice, that design 
is not reflected in the documentation.

Whether the current behaviour is wrong, or the documentation is wrong, 
is irrelevant to the question of whether or not the developers back in 
nineteen-ninety-whatever would have choosen to add dict.get had there 
been syntax for catching the KeyError in an expression. Perhaps they 
would have argued:

"Sure, you can catch the KeyError yourself, but 'get' is a fundamental 
operation for mappings, and I think that dict should implement a 'get' 
method just to be complete."

Or perhaps not. Some developers prefer minimalist APIs, some developers 
prefer more exhaustive APIs.

Regardless of what might have happened back in 199x when dict.get was 
first discussed, I think we can agree that an except expression will 
lower the pressure on Python to add *more* "get-like" methods, or add 
default arguments, in the future.



-- 
Steven


More information about the Python-Dev mailing list