0: Хорошо, давайте посмотрим на все это со стороны питона, да, потому что из идеи хэш таблиц и собственно реализации словаря через хэш таблицы в питоне во многих других языках программирования.
1: Можно найти некоторые следствия, неприятные моменты. Вот возьмём код на питоне, где мы определяем класс person, который имеет какие-то внутренние свои поля. Собственно, нейм эйдж, сами прайвет и
2: И поля это что это переменная экземпляра объекта, да, то есть мы говорим, что вот эти вот поля, вот эти вот переменные, они будут храниться в по сути, каждом каком-то конкретном объекте данного класса, когда мы создадим эти экземпляры и
3: Собственно, я это и делаю в 9 и в 12 строчках, да, я создаю человека ивана возраста 20 и создаю человека, джон возраста 20, да, можно добавить ещё петра, максима и так далее. Нам это сейчас не принципиально закидываю.
4: В словарик, собственно, в качестве ключа использую кастомный тип, да, свой, который я реализовал не строчки уже и также в качестве ключа здесь использую джона и закидываю туда 3, 2, 1.
5: Естественно. Ну, консоль, да, я выведу не что-то очень красивое, хотя в плане примеров, в некоторых заданиях вы уже видели вариант того, как можно сделать красивый вывод в консоль и
6: Собственно я получил что-то такое, да в принципе мы здесь что видим мы видим, что есть какой-то объект соответствующего класса персон из модуля main, да, по какому-то, по сути адресу это все вроде как логи.
7: Мы это с вами все это видели. Вот вам ключ, вот вам значение, вот ключ, вот значение. Хорошо. Но что если я немножечко изменю свой код?
8: Ну так, совсем чуть чуть. То есть я возьму и 2 человека, назову тоже иваном. Более того, возраст 120. И более того, приватное поле, самфинг, прайвет у нас и там, и там тест. То есть, по сути, у меня есть 2 разных объекта, которые
9: Которые находятся в разных местах в памяти. Ну, мы потому что создали независимо, да, нет никакого такого скрытого механизма, который мы сами или python, да, в реализации си пайтона, который мы используем как бы
10: И каким-то образом там объединил бы их. Ну нет такого механизма, во всяком случае, по умолчанию. Поэтому мы получаем следующую картину. Мы также имеем 2 элемента в нашем массиве, в нашем словаре персон по 1
11: Адресу и person по другому адресу. Ну, по другому, с другим айдишником, 123, 321. Желаемое ли это поведение? Ну, как бы, как посмотреть, да?
12: То есть, если задуматься, у нас, по сути, есть 2 объекта персон, которые, да, фактически с точки зрения нашей программы являются разными объектами, они находятся в разных местах, в оперативной памяти, но
13: С точки зрения логики программы, с точки зрения бизнес, скажем так, логики, с точки зрения логики реального мира, мы понимаем, что если у нас есть 2 человека, которые вот характеризуются только именем и возрастом, и каким-то там скрытым
14: Набором данных право, и они полностью идентичны.
15: То, видимо, это, наверное, одинаковый будет, собственно, человек. Правильно? Ну, как мы можем это сделать? Как-то так, да, то есть я просто сказал, что у нас
16: Есть ещё номер паспорта и так получается, что вот этот person и вот этот person они в общем то имеют одинаковое имя, одинаковый возраст и одинаковый номер паспорта да, с точки зрения программы это разные объекты с точки зрения логики немножечко
17: Утрированный, придуманный, но с точки зрения логики реального мира, это вполне себе один и тот же человек, когда такое возможно. Ну, когда мы, например, имеем базу данных,
18: Понимаете? То есть у нас внутри базы данных есть какая-то запись для этого человечка. У нас есть объект, ну, в оперативной памяти базы данных, очевидно, да, который представляет, да, соответствует нашему Ивану потом.
19: К этой базе данных мы обращаемся, создаём запрос в этом запросе мы также как бы создаём некий объект, то есть набор данных, по сути, который соответствует нашему Ивану. Мы в питоне в нашей программе.
20: Создаём объект, который соответствует нашему Ивану. То есть реальная сущность, да, или сущность внутри как бы вашей игры, к примеру, да, где есть какой-то внутренний мир, она может существовать в единственном экземпляре фактически.
21: Как бы она может ей могут соответствовать какие-то разные объекты. Это может быть связано. Вот как я привёл пример с базой данных, да, или с какими-то другими нюансами реализации, например, в плане оптимизации вы вместо того, чтобы копировать туда постоянно данные
22: Из 1 места в другое просто имеете несколько дубликатов. 1 одинаковых может вызвать проблемы, но не важно нам сейчас это. То есть вполне себе такое возможно. Что же нам делать? И вообще, как если у нас словари используют какой-то хэш, то
23: Откуда этот хэш берётся и в питоне? Этот хэш, в общем то, по умолчанию соответствует вашему id и выглядит как-то так. То есть в 1 строчке мы опять-таки видим наш словарик во 2.
24: Строчке мы видим id нашего объекта x. В 3 строчке мы видим id нашего объекта x делён на 16, да и, собственно, в 4 видим
25: Hash нашего объекта. X y. Последние 2 строчки соответствуют. То есть, конечно, не напрямую соответствуют хэш айдишнику, но между ними есть некое соответствие. Да, хэш основывается на айдишнике. Это стандартное поведение, то есть, грубо говоря, по
26: Мы сравниваем по ссылке наши объекты. В данном случае, окей, но как нам это переопределить? И для этого нам необходимо переопределить 2 метода и
27: И, собственно, хэш, да, сейчас посмотрим, какие именно и как, например, это можно сделать так. То есть, что я изменил, я добавил 2 метода хэш и и икво, да, сокращение.
28: По сути, здесь, но это довольно странные методы. Почему? Потому что у них двойное подчёркивание, да, и вы уже знакомы с некоторыми из этих методов. Например, инет. Некоторые другие встречались в заданиях, но
29: Что это за двойные подчёркивания? Это так называемые дандер методы, да, магические методы. Дандер, дабл андерскор, то есть двойное, нижнее подчёркивание, банальное название. Но что поделать, это те самые методы, которые вызываются, когда
30: Вы, собственно, вызываете некоторые встроенные функции, то есть, если мы возьмём, например, функцию hash икс, да, которую я перед этим показывал, хэш икс по умолчанию попытается вызвать нашу вну.
31: Как бы функцию внутри этого класса хэш, а у соответственно, экземпляра икс, то есть, грубо говоря, вот это эквивалентно икс точка там ддд хэш и так далее.
32: Мы можем также применить и собственно Лэн, да, то есть когда мы вызываем лен от какого-то листа, мы по факту обращаемся к методу двойное нижнее, подчёркиваю.
33: Не learn, как ни странно. И более того когда мы говорим о каких-то параметрах, в смысле о каких-то операторах которые мы вызываем у объекта или применяем к объекту какие
34: И, ну, например, сравнение, умножение, например, я бы мог написать икс, умножить на игрек, но в таком варианте я бы не смог умножить человека на человека, да, мне необходимо было бы реализовать
35: Дандер метод мульт мультиплай, то есть умножить, да, и тоже самое с оператором вызова.
36: Я мог бы его переопределить, да, я мог бы переопределить поведение квадратных скобок и так далее, и тому подобное. То есть, на самом деле многие какие-то вот стандартные, встроенные в язык функции, возможности, к которым вы привыкли, они реа.
37: Реализуются через вот такие дандер методы. Я думаю, мы о них ещё поговорим. И, собственно, теперь, когда вы понимаете, как реализуются хэш таблицы, да, в
38: Вне зависимости от того, в принципе, как именно там, например, ченнингом или другими альтернативными вариантами, мы располагаем какие-то элементы, которые попали в один и тот же Бакет. Вне зависимости от этого у нас есть проблемы.
39: Потому что если мы у кастомного класса реализовали класс, метод hash, но не реализовали метод иквел или, наоборот, при использовании этих классов в стандартных словарях, да и на самом деле во многих других местах.
40: Потому что, ну, кто знает, кто будет использовать хэш нашего объекта или попытается сравнить наш объект с каким-то другим. Но самый простой пример это именно словари. Так вот, если мы не реализуем эти методы одновременно,
41: То мы можем попасть в ситуацию, когда у нас будут ломаться, собственно, как я уже сказал, словари. Почему? Ну, потому что, как мы находим, куда нам надо положить очередную пару ключ значения с помощью хэша.
42: Как мы понимаем, что мы нашли ключ, который, собственно, запрашивает у нас пользователь с помощью хэша и сравнения, потому что если вот в данный момент, да, Иван,
43: Допустим, и Максим имеют одинаковый хэш, но это же разные ключи. Да, мы понимаем, что одинаковый хэш не говорит о том, что у нас, собственно, ой, как бы одинаковые объекты точно совпадают. Все хорошо. Нет, это говорит о том, что они, скорее всего, совпадают, как бы
44: Такая вот проблема с хэшами, напоминаю. И, соответственно, надо сравнить 2 этих объекта, а тут могут быть не строки, тут могут быть кастомные объекты. Соответственно, нам практически всегда при реализации кастомной какой-то
45: Хэш функции хэширования для вашего объекта необходимо переопределить сравнение собственно ваших 2 классов. То есть в данном случае я сравниваю, а у меня имя и возраст совпадают у 2 разных людей или нет.
46: Как бы, ну, если совпадает, то все хорошо. Ну, то есть можно добавить ещё паспорт намбер, как бы, хотя тут вот как раз-таки нюанс, о котором я говорил в самом ещё, в принципе, начале. То есть, обратите внимание, во первых, тут несколько нюансов я могу использовать
47: Стандартную функцию хэширования.
48: Какая именно хэш функция там зависит от многих нюансов. То есть, например, к строкам и некоторым другим типам данных у нас добавляются ещё определённые
49: Рандомные данные, да, или рандомно выбирается хэш функция, что связано с некоторыми проблемами рантайма. То есть, например, как бы, если вы, да, например, используете где-то питон на бэкенде и
50: Злоумышленник знает, собственно, вашу хэширую ую функцию и он знает, что вы её точно применяете где-то там, например, при регистрации пользователей, то он может подобрать
51: Таких пользователей, да, из, ну, собственно, в качестве таких фантомных пользователей, да, зарегистрировать вам такое огромное количество пользователей специально подобранных, что они все попадут в 1 Бакет, да, или же они, наоборот, как-то
52: Займут все возможные бакеты, да, они равномерно займут, но все бакеты будут заняты, и это приведёт к замедлению вашей хэш таблицы, вашего словаря и, соответственно, к замедлению вашего бэкенда. Да, это немножко упрощённый так сильно.
53: Упрощённый пример, но, тем не менее, такое вполне возможно и для того, чтобы вот таких проблем не было, или каких-то аналогичных проблем, собственно, для некоторых типов данных, как строки там, дейттайм, ещё какие-то используются, что
54: Рандомное значение. Ну вообще на самом деле так называемая соль salt. Немножечко сейчас отойдём но все-таки я расскажу вкратце о чем, в чем идея. Собственно, это какое-то рандом
55: Умная, да, или специально подобранная, но, скажем так, индивидуально для вашего проекта специально подобранная там
56: Значение, которое вы добавляете к, например, строкам абц плюс собственно ваш солт да, это могут быть наборы чисел. Это могут быть какие-то кастомные строки или наборы вообще рандомных символов.
57: Но это какой-то рандомный такой кусочек информации, которая, который меняет вашу оригинальную информацию для того, чтобы вы не могли, например, зная хэш функцию подобрать
58: По известному хэшу оригинальное значение.
59: Ну, это уже идут как бы вопросы, криптоанализа безопасности. И там, к примеру, к хэш функциям предъявляются гораздо более строгие, более обширные требования, да, то есть мы не можем рандомную, абсолютно любую
60: Функцию использовать для того, чтобы шифровать наши пароли, да и как раз-таки хранение паролей в зашифрованном каком-то виде с использованием соли это довольно классический пример.
61: Скажем, применение безопасности, да, и использование нюансов собственно хэширования, ну, возвращаясь обратно к нашему хэшу, то есть по умолчанию в случае каких-то других.
62: Будет применена некая хэш функция из стандартного набора, там зависит от платформы, зависит от того, какой именно запуск и так далее. И уже, соответственно, мы можем сказать, что слушай, а посчитай мне hash, например, от
63: Набора вот моих полей, причём набор полей я указываю не все поля, да, такое вполне себе возможно, да, то есть в данном случае, конечно, с паспортом не очень хороший пример, да, но если бы у меня какие-то был
64: Внутренние технические данные, которые не влияют на, скажем, идентификацию объекта, да, объекта, не влияют на то, на тот смысл, который представляет из себя объект, то мы можем, в принципе, не
65: Учитывать эти данные в хэше без проблем. И в нашем случае мы такие, так предположим, паспорта нет. Действительно, мы в хэше, да, мы при сравнении, мы при представлении нашего человека в виде какого-то чёткого значения
66: Хотели бы как-то учитывать его имя и возраст, но, например, одежду, в которую он одет, ну, наверное, не очень хочется учитывать, да, потому что он может переодеться и что это другой человек, да нет.
67: Нет, конечно, это тот же самый человек, поэтому одежду мы в хэш не закидывали бы. Вот это такой самый простой вариант того, как можно реализовать хэширование. Плюс минус довольно хорошее, да, далее сравнение, опять-таки, сравнении.
68: У меня тоже идёт по соответствующим полям. То есть я сравниваю по имени и по возрасту. И, как правило, если хэширование, скажем, провалилось в том плане, что мы наткнулись на коллизию, то есть 2 объекта дали одинаковый хэш, нам надо
69: Проверить, что это коллизия точно или это точно не коллизия? Да, мы должны сравнить объекты. И вот здесь как раз-таки вызывается метод иквел, когда мы проходимся по нашему, ну, в данном случае линкит листу.
70: Ну и мы сравниваем здесь имя и возраст. Окей, немножечко заглянули в то, как устроен питон. Естественно, всевозможные нюансы того, как это реализовано, да, как вот
71: У нас, если мы вызываем у у объекта класса персон, допустим персон персон а, ну у нас был какой-то x. Да, пусть будет хэш от x. Где x это экземпляр класса person, да, по сути вот этот.
72: Метод вызывает внутреннюю функцию дандер метод hash, который, в свою очередь, вызывает тоже функцию hash, да, от нашего тепла селф нейм, селф эйдж, да, создаёт тёпл, но как бы там тоже есть некоторые нюансы, то есть
73: Насколько глубоко это все раскрывается, да, если у вас комплексные объекты, окей, такое погружение, скажем так, в глубины применения хэша и комбинации всевозможных хэшей в 1 большой хэш какого-то
74: Более крупного объекта может быть запутанным, да, но обратите внимание, даже вот в этом примере я по факту, к чему свёл, я свёл расчет хэша к набору строк, ну, в данном случае к набору из строки
75: И возраста, и они, в свою очередь, были соединены к отдельным битам.
76: Никуда от этого не убежать.
77: Кстати, о ноликах, единичках. А если хэш функция, да, это функция, которая выдаёт вам строку установленной длины, то какая установленная длина в питоне? Ну это
78: На самом деле вполне можно чекнуть довольно легко, да, импортируем, соответственно, сис и там у нас в хэш инфо есть ширина. Вот естественно, эта ширина зависит от платформы, на которую вы запускаете, но
79: Например, у меня это 64 окей. Если посмотреть, то хэши на
80: Моей платформе будут занимать 64 бита, а не 64 циферки, не 64 там числа или ещё чего-то. 64 бита. То есть даже в питоне вы с ними все равно можете так или иначе напрямую встретиться.
81: Ещё, кстати, 1 нюансик, который, думаю, стоит вслух проговорить полноценно.
82: Не очень хорошая идея вообще. Да, вот как раз-таки в этом примере это видно, не очень хорошая идея.
83: Реализовывать хэш на основе полей, которые могут быть изменены.
84: О, почему? Но опять-таки все сводится к тому, как работает словарь. То есть, представьте себе ситуацию, когда я вот определил таким образом класс person, все хорошо, все работает прекрасно. А потом я написал что-то в духе типа x.
85: Name упс. Ну там у меня немножко обрезал скалдо, и x name равняется там абц. У меня был Пётр. Да, здесь Иван. Окей, ладно, давайте возьмём Пётр, чтобы не сильно каляка.
86: Там Пётр i предположим, его хэш на основе его имени и возраста давал мне там значение 15 теперь я изменил имя на абц, и этот же объект, который изначально был петром, у меня теперь.
87: Является абц 20, да, где 20 это года, как было в примере до этого. И hash другой. Ну, например, хэш какой-нибудь. Ну тут у нас правда, давайте не 15 все-таки будем соответствоват.
88: Нашему бакету. Ну, например, окей, пусть будет 2. Теперь же, если мы заменяем петра на абц, наш новый хэш, предположим, 4.
89: И теперь представьте себе, я беру и говорю слушай, а у тебя я то как программист знаю, что я изменил петра на абц. Я спрашиваю у словаря слушай, а у тебя есть?
90: Такой ключ абц. И мы что делаем? Мы считаем хэш от абц. Хэш от абц даёт нам, как мы поняли, 4. Мы такие, окей. В каком бакете ты лежишь 4 на 5? Даёт нам 4, в 4 0 1.
91: 1, 2, 3, 4. Нет, у нас нет такого значения.
92: Ну, в смысле, как же я, я ж точно закидывал в тебя петра, который даёт, который, да, изначально давал значение 2, но теперь то даёт 4.
93: И эту проблему мы могли бы как-то решить, то есть мы могли бы пытаться отслеживать изменения каждого объекта в нашем словаре.
94: Как бы вспоминая то, что я буквально пару минут назад поднимал в плане темы, то, что у нас вложенность, то может быть огромной, и отслеживать изменение объекта, это на самом деле не очень тривиальная задача.
95: Ну вот тут начинаешь как-то задумываться о том, а, наверное, все-таки проще не делать хэш на основе тех данных, которые могут изменяться. То есть надо, значит, каким-то образом, да, заблокировать возможность менять нейм эйдж, да, через приватные поля, которые
96: Конечно, можно к ним получить доступ тем или иным образом, но мы об этом поговорим попозже, потому что, я думаю, нам вполне нужно вспомнить некоторые нюансы с классами соуп в питоне и некоторые другие принципы, которые так или иначе, косвенно или прямо.
97: Связанных с