2014-12-12 3 views
6
>>> class Thing(object): 
...  def __index__(self): 
...   return 1 
... 
>>> thing = Thing() 
>>> list_ = ['abc', 'def', 'ghi'] 
>>> list_[thing] 
'def' 
>>> dict_ = {1: 'potato'} 
>>> dict_[thing] 
# KeyError 

Как вещь знает, чтобы представлять себя как 1 при доступе по списку, но не по типу? Не оба магических метода проходят через __getitem__? Использование, показанное для списков, могло бы пройти через __int__, а что такое raison d'être для __index__ в любом случае?Python __index__ special method

+3

Для полного обоснования метода '__index__' вы можете прочитать [PEP 357] (https://www.python.org/dev/peps/pep-0357/). В принципе, '__int__' принуждает результат к целочисленному типу.Представьте, что вы написали что-то вроде 'x [3.2: 5.8]'. Это должно вызвать ошибку, и в Python это делает. Однако, если логика индексирования использовала '__int__', она бы привела значения среза к' 3' и '5', и код не вызвал бы ошибку. –

ответ

8

Dict и List не реализует __getitem__ таким же образом. Объекты Dict используют сравнение (__eq__) на __hash__ объектов в качестве ключа для использования в __getitem__.

Чтобы сделать Thing пригодным для использования, вы должны реализовать как хэш, так и экв.

+5

В то время как 'dict' использует хеши, вы не сможете заставить это вести себя так, как вы хотите, просто внедряя' __hash__' ... Я считаю, что вам также нужно '__eq__'. – kindall

+1

Да, это так. Я отредактировал это. –

9

@ BenoîtLatinier был прав, когда он сказал:

Dict и список не реализует __getitem__ таким же образом.

Однако я хотел бы добавить дополнительную информацию. Согласно documentation:

object.__index__(self)

Вызывается для реализации operator.index(), и всякий раз, когда Python необходимо преобразовать без потерь числового объекта до целого объекта (, такие как в нарезке, или во встроенном в bin(), hex() и oct() функции). Наличие этого метода указывает, что числовой объект является целым типом. Необходимо вернуть целое число.

Часть, выделенная жирным шрифтом, важна. Индексирование и нарезка в списке обрабатываются одним и тем же методом (а именно, __getitem__). Таким образом, если для нарезки вызывается Thing.__index__, он также будет вызван для индексирования, поскольку мы используем тот же метод. Это означает, что:

list_[thing] 

примерно эквивалентно:

list_[thing.__index__()] 

Для словаря однако Thing.__index__ не вызывается (нет никаких оснований называть его, так как вы не можете нарезать словарь). Вместо этого выполнение dict_[thing] говорит Python, чтобы найти ключ в словаре, который является самим экземпляром thing. Так как этого не существует, возникает KeyError.

Возможно демонстрация будет полезно:

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

А почему __index__ существует в первую очередь, причина тщательно перечислены в PEP 0375. Здесь я не буду повторять все это, но в основном это так, что вы можете разрешить произвольные объекты служить целыми числами, которые необходимы для нарезки, а также для нескольких других приложений.