Оскільки списки можна змінювати, dict
ключі (і set
члени) мають бути хешуваними, а хешування змінних об’єктів - погана ідея, оскільки значення хешу слід обчислювати на основі атрибутів екземпляра.
У цій відповіді я наведу декілька конкретних прикладів, які, сподіваюся, додадуть значення поверх існуючих відповідей. Кожне розуміння стосується і елементів set
структури даних.
Приклад 1 : хешування змінного об'єкта, де хеш-значення базується на змінній характеристиці об'єкта.
>>> class stupidlist(list):
... def __hash__(self):
... return len(self)
...
>>> stupid = stupidlist([1, 2, 3])
>>> d = {stupid: 0}
>>> stupid.append(4)
>>> stupid
[1, 2, 3, 4]
>>> d
{[1, 2, 3, 4]: 0}
>>> stupid in d
False
>>> stupid in d.keys()
False
>>> stupid in list(d.keys())
True
Після мутації stupid
його більше не можна знайти в дикті, оскільки хеш змінився. Знаходить лише лінійне сканування списку ключів дикту stupid
.
Приклад 2 : ... але чому не просто постійне хеш-значення?
>>> class stupidlist2(list):
... def __hash__(self):
... return id(self)
...
>>> stupidA = stupidlist2([1, 2, 3])
>>> stupidB = stupidlist2([1, 2, 3])
>>>
>>> stupidA == stupidB
True
>>> stupidA in {stupidB: 0}
False
Це теж не гарна ідея, оскільки рівні об’єкти повинні хешувати однаково, щоб їх можна було знайти в dict
або set
.
Приклад 3 : ... добре, а як щодо постійних хешів у всіх інстанціях ?!
>>> class stupidlist3(list):
... def __hash__(self):
... return 1
...
>>> stupidC = stupidlist3([1, 2, 3])
>>> stupidD = stupidlist3([1, 2, 3])
>>> stupidE = stupidlist3([1, 2, 3, 4])
>>>
>>> stupidC in {stupidD: 0}
True
>>> stupidC in {stupidE: 0}
False
>>> d = {stupidC: 0}
>>> stupidC.append(5)
>>> stupidC in d
True
Здається, справи працюють, як очікувалося, але подумайте, що відбувається: коли всі екземпляри вашого класу видають одне і те ж хеш-значення, ви матимете колізійне зіткнення, коли в a dict
або в присутності буде більше двох екземплярів як ключі set
.
Щоб знайти правильний екземпляр з my_dict[key]
або key in my_dict
(або item in my_set
), потрібно виконати стільки перевірок рівності, скільки є екземплярів stupidlist3
у ключах dict (у гіршому випадку). На даний момент мета словника - пошук O (1) - повністю переможена. Це демонструється у наступні терміни (зроблено за допомогою IPython).
Деякі терміни для прикладу 3
>>> lists_list = [[i] for i in range(1000)]
>>> stupidlists_set = {stupidlist3([i]) for i in range(1000)}
>>> tuples_set = {(i,) for i in range(1000)}
>>> l = [999]
>>> s = stupidlist3([999])
>>> t = (999,)
>>>
>>> %timeit l in lists_list
25.5 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit s in stupidlists_set
38.5 µs ± 61.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit t in tuples_set
77.6 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Як бачите, тест на членство у нас stupidlists_set
навіть повільніший, ніж лінійне сканування в цілому lists_list
, тоді як у вас очікуваний надшвидкий час пошуку (коефіцієнт 500) у наборі без навантажень хеш-зіткнень.
TL; DR: ви можете використовувати tuple(yourlist)
як dict
ключі, тому що кортежі незмінні та розмиваються.