Менее известная идея словаря 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<=y
callsx.__le__(y)
,x==y
вызовыx.__eq__(y)
, _16 _ звонкиx.__ne__(y)
,x>y
callsx.__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
.
Теперь вы знаете, что происходит за кулисами.
Надеюсь, вам это пригодится.