On 3/5/2014 8:15 PM, Steven D'Aprano wrote:
On Wed, Mar 05, 2014 at 12:57:03PM -0800, Thomas Wouters wrote:
On Thu, Feb 27, 2014 at 1:29 PM, Chris Angelico <ros...@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]

This is a buggy special method. According to the docs for hash and __hash__ and the general convention on exceptions, a __hash__ method should return an int or raise TypeError.

...     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

This breaks the implied C invariant and makes the object 'a' incoherent and buggy

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.

One could argue that an error raised in a special method is not raised *by* a function that uses the special method. The docs constantly assume that special methods are coded correctly.

'''bool([x])
Convert a value to a Boolean, using the standard truth testing procedure. If x is false or omitted, this returns False; otherwise it returns True.'''

... unless x.__bool__ raises or returns something other than True/False -- in which case bool itself raises.
TypeError: __bool__ should return bool, returned int

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

It appears to be actually equivalent to

key_hash = hash(key)
try:
    return d._hashlookup(key_has)
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,

Given that the choice is that bugs in special methods should not pass silently, I would presume that it is intentional.

>  that design is not reflected in the documentation.

The docs generally describe behavior in the absence of coding errors.

--
Terry Jan Reedy

_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to