Предположим, у нас есть сервер и его клиенты. Мы хотим отслеживать состояние клиентов на сервере и управлять ими. Для этой цели мы будем добавлять клиентов в коллекцию, чтобы избежать случайного дублирования одного и того же клиента на сервере.
Давайте создадим класс Client и добавим его в коллекцию (я также добавил метод repr для красивого вывода):
class Client:Замечательно, всё работает как задумано! Теперь попробуем добавить второго клиента и убедимся, что дублирования не происходит:
def init(self, user_name):
self.user_name = user_name
def repr(self):
return self.user_name
fish1 = Client(user_name="catfish")
clients = set()
clients.add(fish1)
print(clients) # {catfish}
fish2 = Client(user_name="catfish")Как это возможно? Мы добавили два абсолютно идентичных экземпляра в коллекцию, ожидая, что останется только один, но оба остались.
clients.add(fish2)
print(clients) # {catfish}
При добавлении объекта в коллекцию интерпретатор следует следующему правилу:
если a == b, то hash(a) == hash(b) должно быть обязательно выполнено.
То есть, интерпретатор сравнивает объекты не только напрямую, но также сравнивает их хеши.
Для сравнения объектов, коллекции и словари используют магический метод eq. Для пользовательских объектов этот метод определен по умолчанию. По умолчанию, два разных объекта не считаются равными, даже если они абсолютно идентичны:
print(fish1 == fish2) # FalseПо умолчанию все объекты в Python также имеют хеш-значение (хеш), которое рассчитывается из их идентификатора (id). Когда мы пытаемся добавить объект в коллекцию, мы используем магический метод hash этого объекта, который также определен по умолчанию. Часто люди думают, что хеш объекта совпадает с его адресом в памяти, но это не всегда так:
🦑 В Python 2.6 и более ранних версиях hash(x) = id()
🦑 В Python 2.6 и более поздних версиях: (https://bugs.python.org/issue5169) hash(x) == id(x)/16
То есть, нельзя полагаться на то, что hash(x) = id().
Однако можно полагаться на то, что в Python у объектов по умолчанию есть хеш-значение, которое зависит от их идентификатора (id), и что хеш объекта не меняется в течение его жизни.
Так как в нашем примере объекты разные и занимают разные ячейки памяти, мы получаем:
print(hash(fish1), hash(fish2)) # 8786876890805 8786876904409Так что можно сделать, чтобы объекты, которые мы считаем одинаковыми, объединялись в коллекции? Нужно переопределить методы hash и eq, чтобы явно указать интерпретатору, как сравнивать объекты и как рассчитывать хеш.
Для этого внесем изменения в определение класса:
class Client:Теперь, при добавлении объектов в коллекцию, всё работает так, как ожидалось:
def init(self, user_name):
self.user_name = user_name
def repr(self):
return self.user_name
def hash(self):
return hash(self.user_name)
def eq(self, other):
if self.user_name == other.user_name:
return True
else:
return False
clients = set()Помните, чтобы добавить объект вашего собственного класса в коллекцию или словарь в Python, вам необходимо:
fish1 = Client(user_name="catfish")
clients.add(fish1)
fish2 = Client(user_name="catfish")
clients.add(fish2)
print(clients) # {catfish}
🦑 Переопределить и метод hash, и метод eq, иначе это не будет работать (https://docs.python.org/3/reference/datamodel.html#object.__hash__)
🦑 Если объекты идентичны, их хеши должны быть равными.
🦑 Хеш-функция должна содержать информацию, которая однозначно идентифицирует объект.
🦑 Хеш объекта не должен изменяться в течение его жизни (иначе могут возникнуть проблемы во время выполнения программы).