Автоматическое создание класса hashable
Существует несколько стандартных способов сделать класс хэшируемым, например (заимствование у SO):
# assume X has 2 attributes: attr_a and attr_b
class X:
def __key(self):
return (self.attr_a, self.attr_b)
def __eq__(x, y):
return isinstance(y, x.__class__) and x.__key() == y.__key()
def __hash__(self):
return hash(self.__key())
Теперь предположим, что у меня есть много классов, которые я хочу сделать хэшируемыми. Все они неизменяемы, имеют неизменные атрибуты, и допустимо хэширование всех этих атрибутов в массе (для класса со слишком большим количеством атрибутов мы бы хотели хэшировать только несколько атрибутов, которых достаточно, чтобы избежать большинства коллизий). Могу ли я избежать написания __key()
метод вручную для каждого класса?
Было бы хорошей идеей сделать базовый класс, который определяет __key()
, __eq__
, а также __hash__
для них? В частности, я не уверен, найти ли все атрибуты экземпляра, которые должны войти в __hash__
выполнимо Я знаю, что это вообще невозможно, но в этом случае мы можем предположить больше об объекте (например, он неизменен - после __init__
закончено, все его атрибуты можно хэшировать и т. д.).
(Если иерархия наследования не будет работать, возможно, будет работать декоратор?)
2 ответа
Экземпляры хранят свои атрибуты в self.__dict__
:
>>> class Foo(object):
... def __init__(self, foo='bar', spam='eggs'):
... self.foo = foo
... self.spam = spam
...
>>> f = Foo()
>>> f.__dict__
{'foo': 'bar', 'spam': 'eggs'}
При условии, что вы не храните какие-либо методы в своих экземплярах, по умолчанию .__key()
может быть:
def __key(self):
return tuple(v for k, v in sorted(self.__dict__.items()))
где мы сортируем элементы по имени атрибута; tuple()
вызов гарантирует, что мы возвращаем неизменную последовательность, подходящую для hash()
вызов.
Для более сложных установок вам придется либо проверить типы, возвращаемые values()
(пропустить функции и т. д.) или использовать определенный шаблон атрибутов или перепрофилировать __slots__
перечислить соответствующие атрибуты, которые вы можете использовать.
Вместе с вашим __hash__
а также __eq__
методы, которые сделали бы хороший базовый класс для наследования для всех ваших неизменных классов.
Вы можете сделать это, если вы примете соглашения для своих атрибутов. В вашем примере это было бы довольно тривиально, поскольку ваши атрибуты начинаются с "attr_" - поэтому вы можете написать свой метод __key как:
def __key(self):
return tuple (getattr(self, attr) for attr in self.__dict__ if attr.startswith("attr_") )
Как вы можете видеть - любой тест, который вы можете найти для установки условия фильтра выражения генератора, будет соответствовать вашим потребностям.
Я могу предложить вам, чтобы у вас были занятия с использованием Python. __slots__
особенность: это не только облегчит поиск имен ваших атрибутов, но и сделает ваши неизменяемые объекты более эффективными в использовании и с меньшим объемом памяти.
class X:
__slots__ = ("a", "b", "c")
def __key(self):
return tuple (getattr(self, attr) for attr in self.__class__.__slots__ )
edit Отвечая на первый комментарий от ОП:
Это работает с наследованием, конечно. Если вы всегда будете использовать для них все атрибуты объекта, вам не понадобится часть выражения "если" - напишите функцию как _key
(вместо __key
который создает уникальное имя для каждого класса внутри класса в верхней части вашей иерархии, и он будет работать для всех ваших классов.