>>> class Thing(object):
... def __index__(self):
... return 1
...
>>> thing = Thing()
>>> list_ = ['abc', 'def', 'ghi']
>>> list_[thing]
'def'
>>> dict_ = {1: 'potato'}
>>> dict_[thing]
# KeyError
How does thing
know to represent itself as 1 when accessed by a list, but not by a dict? Don't both magic methods go through __getitem__
? The usage shown for lists could go through __int__
instead so what is the raison d'être for __index__
anyway?
@BenoîtLatinier was correct when he said:
Dict and List does not implement
__getitem__
the same way.
However, I'd like to add a little more information. According to the documentation:
object.__index__(self)
Called to implement
operator.index()
, and whenever Python needs to losslessly convert the numeric object to an integer object (such as in slicing, or in the built-inbin()
,hex()
andoct()
functions). Presence of this method indicates that the numeric object is an integer type. Must return an integer.
The part I bolded is important. Indexing and slicing on a list are both handled by the same method (namely, __getitem__
). So, if Thing.__index__
is called for slicing, it will likewise be called for indexing since we are using the same method. This means that:
list_[thing]
is roughly equivalent to:
list_[thing.__index__()]
For the dictionary however, Thing.__index__
is not being called (there is no reason to call it since you cannot slice a dictionary). Instead, doing dict_[thing]
is telling Python to find a key in the dictionary that is the thing
instance itself. Since this doesn't exist, a KeyError
is raised.
Perhaps a demonstration will be helpful:
>>> class Thing(object):
... def __index__(self):
... print '__index__ called!'
... return 1
...
>>> thing = Thing()
>>> list_ = ['abc', 'def', 'ghi']
>>> list_[thing] # __index__ is called
__index__ called!
'def'
>>>
>>> dict_ = {1: 'potato'}
>>> dict_[thing] # __index__ is not called
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: <__main__.Thing object at 0x01ACFC70>
>>>
>>> dict_ = {thing: 'potato'} # Works if thing is a key
>>> dict_[thing]
'potato'
>>>
As for why __index__
exists in the first place, the reason is thoroughly listed in PEP 0375. I won't repeat all of it here, but basically it is so that you can allow arbitrary objects to serve as integers, which is needed in slicing as well as a few other applications.