Автоматическое создание класса 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 который создает уникальное имя для каждого класса внутри класса в верхней части вашей иерархии, и он будет работать для всех ваших классов.

Другие вопросы по тегам