Менее известная идея словаря Python

Я программировал на Python, работая инженером по компьютерному зрению. Обычно я просматривал Документы стандартной библиотеки, читал статьи, книги, чтобы улучшить свои навыки Python.

Если вы знакомы с каким-либо языком, вы наверняка сталкивались со структурой данных словаря. Цитировать:

Словарь - это ассоциативный массив (также известный как хеши). … Значения словаря могут быть любого типа данных Python. Итак, словари - это неупорядоченные пары "ключ-значение".

Если вы хотите начать работу со словарем Python, вы можете сослаться на этот подробный пост в блоге.
Недавно я наткнулся на это словарное выражение, которое дает полезное представление о внутренней работе словарей.

>>> {1: 'first', 1.0: 'second'}

Что оценивает приведенное выше выражение? Прежде чем ответить, давайте разберемся с некоторыми концепциями, чтобы лучше подготовиться к ответу.

Когда Python обрабатывает любое словарное выражение, он начинает с создания пустого объекта словаря, а затем назначает ключи и значения в порядке, указанном в словарном выражении. Например:

>>> d = {1:'one', 2:'two', 3:'three'}
----------evaluates to----------
>>> d = dict()
>>> d[1] = 'one'
>>> d[2] = 'two'
>>> d[3] = 'three'

Словари проверяют равенство, а также сравнивают хеш-значение, чтобы определить, совпадают ли два ключа. Равенство проверяется __eq__()методом, а хеш-значение - __hash__(). Чтобы подробнее остановиться на этом, давайте рассмотрим пример. Ниже приведен класс EqualObj, объект которого всегда будет возвращать True по сравнению с любым другим объектом (поскольку __eq__(self) возвращает True каждый раз). Однако хеш-значение двух объектов будет различным, поскольку метод __hash__(self) возвращает id() объекта. В Python id() возвращает адрес объекта в памяти, который всегда уникален.

>>> class EqualObj:
    def __eq__(self, obj):
        return True
def __hash__(self):
        return id(self)

Если мы поиграем с объектами вышеуказанного класса:

>>> obj1 = EqualObj()
>>> obj2 = EqualObj()
>>> obj1 == obj2
True
>>> obj1 == 'anyObject'
True
>>> hash(obj1)
140698402706224
>>> hash(obj2)
140698402705984
>>> hash(obj1) == hash(obj2)
False

Взято из стандартной документации Python

Это так называемые методы «богатого сравнения». Соответствие между символами операторов и именами методов следующее: x<y вызовы x.__lt__(y), x<=ycalls x.__le__(y), x==y вызовы x.__eq__(y), _16 _ звонки x.__ne__(y), x>ycalls x.__gt__(y) и x>=y звонки x.__ge__(y).

Отныне obj1 == obj2 возвращает True в приведенном выше случае.

если мы создадим словарь с объектами вышеуказанного класса в качестве ключей:

>>> d = {obj1: 'first', obj2: 'second'}
>>> d
{obj1: 'first', obj2: 'second'}

Следовательно, dict d сохраняет оба ключа, поскольку hash ключей были разными, несмотря на то, что объекты были идентичны. Если мы создадим словарь двух объектов с одинаковым хешем, но сами объекты не будут равны, мы увидим такое же поведение. Снова рассмотрим приведенный ниже класс.

class EqualHashObj:
    def __hash__(self):
        return 1

Создание экземпляра вышеуказанного класса:

>>> obj1 = EqualHashObj()
>>> obj2 = EqualHashObj()
>>> obj1 == obj2       # x == y calls x.__eq__(y)
False
>>> hash(obj1)
1
>>> hash(obj2)
1
>>> hash(obj1) == hash(obj2)
True
>>> d = {obj1: 'first', obj2: 'second'}
>>> d
{obj1: 'first', obj2: 'second'}

Мы наблюдали такое же поведение, если у нас есть словарь с ключами в качестве объекта EqualHashObj.

Словари Python не обновляют сам ключевой объект при обновлении соответствующего значения ключа. Если два ключевых объекта оценены как идентичные (__eq__() & __hash__() возвращает True), то словарь Python не обновит ключевой объект. Будет обновлено только значение. Это показано в примере ниже. Такое поведение согласуется с оптимизацией производительности, поскольку нет необходимости без необходимости обновлять ключевой объект, когда он, по сути, такой же. Если вы хотите узнать больше о том, почему это сделано, вам, вероятно, следует проверить структуру данных хеш-таблицы, поскольку словари используют ее для внутренних целей в большинстве языков, и Python не является исключением.

>>>d = {1: 'first'}
>>>d[1.0] = 'second'
>>>d
{1: 'second'}

Поскольку ключи 1 и 1.0 идентичны, ключ 1 не будет обновлен, будет обновлено только значение на second
Следовательно, наше первое словарное выражение >>> d = {1: 'first', 1.0: 'second'}
будет оцениваться как d = {1:'second'}. Будет присутствовать только один ключевой объект 1, и его значение будет последним обновленным, т.е. second.
Теперь вы знаете, что происходит за кулисами.
Надеюсь, вам это пригодится.