Как сделать класс данных Python хэшируемым? - PullRequest
0 голосов
/ 18 сентября 2018

Скажите, у меня есть класс данных в python3.Я хочу иметь возможность хешировать и упорядочивать эти объекты.

Я хочу, чтобы они заказывались / хэшировались только по id.

Я вижу в документах, что я могу просто реализовать __hash__ и все такое, но я хотел бы, чтобы datacalsses делали всю работу за меня, потому что они предназначены для этого.

from dataclasses import dataclass, field

@dataclass(eq=True, order=True)
class Category:
    id: str = field(compare=True)
    name: str = field(default="set this in post_init", compare=False)

a = sorted(list(set([ Category(id='x'), Category(id='y')])))

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'Category'

Ответы [ 3 ]

0 голосов
/ 18 сентября 2018

Из документов :

Вот правила, регулирующие неявное создание метода __hash__():

[...]

Если eq и frozen оба имеют значение true, по умолчанию dataclass() сгенерирует для вас метод __hash__().Если eq истинно, а frozen ложно, __hash__() будет установлено на None, помечая его как недоступное (что и является изменяемым).Если eq равно false, __hash__() останется нетронутым, что означает, что будет использоваться метод суперкласса __hash__() (если суперкласс является объектом, это означает, что он вернется к хешированию на основе идентификаторов).

Поскольку вы установили eq=True и оставили frozen по умолчанию (False), ваш класс данных не подлежит отмене.

У вас есть 3 варианта:

  • Установите frozen=True (в дополнение к eq=True), что сделает ваш класс неизменным и хэшируемым.
  • Установите unsafe_hash=True, который создаст метод __hash__, но оставит ваш класс изменчивым,таким образом, возникает риск возникновения проблем, если экземпляр вашего класса изменяется при сохранении в файле dict или set:

    cat = Category('foo', 'bar')
    categories = {cat}
    cat.id = 'baz'
    
    print(cat in categories)  # False
    
  • Реализация метода __hash__ вручную.
0 голосов
/ 29 июля 2019

Я хотел бы добавить специальную заметку для использования unsafe_hash.

Вы можете исключить поля из сравнения по хешу, установив сравнение = Ложь или хэш = Ложь.(хэш по умолчанию наследуется от сравнения).

Это может быть полезно, если вы храните узлы на графике, но хотите пометить их как посещенные, не нарушая их хеширование (например, если они находятся в наборе не посещенных узлов ..).

from dataclasses import dataclass, field
@dataclass(unsafe_hash=True)
class node:
    x:int
    visit_count: int = field(default=10, compare=False)  # hash inherits compare setting. So valid.
    # visit_count: int = field(default=False, hash=False)   # also valid. Arguably easier to read, but can break some compare code.
    # visit_count: int = False   # if mutated, hashing breaks. (3* printed)

s = set()
n = node(1)
s.add(n)
if n in s: print("1* n in s")
n.visit_count = 11
if n in s:
    print("2* n still in s")
else:
    print("3* n is lost to the void because hashing broke.")

Мне потребовалось часов , чтобы выяснить ... Полезные дальнейшие чтения, которые я нашел, - это документация по питону для классов данных.В частности, смотрите полевую документацию и документацию arg класса данных.https://docs.python.org/3/library/dataclasses.html

0 голосов
/ 18 сентября 2018

TL; DR

Используйте frozen=True в сочетании с eq=True (что сделает экземпляры неизменяемыми).

Длинный ответ

Из документов :

__hash__() используется встроенным hash() и при добавлении объектов в хешированные коллекции, такие как словарии устанавливает.Наличие __hash__() подразумевает, что экземпляры класса являются неизменяемыми.Изменчивость - это сложное свойство, которое зависит от намерения программиста, существования и поведения __eq__(), а также от значений флагов eq и frozen в декораторе dataclass().

По умолчанию dataclass() будетнеявно добавлять метод __hash__(), если это не безопасно.Он также не добавит и не изменит существующий явно определенный метод __hash__().Установка атрибута класса __hash__ = None имеет особое значение для Python, как описано в документации __hash__().

Если __hash__() не определено явным образом или если для него задано значение Нет, то dataclass()может добавить неявный метод __hash__().Хотя это и не рекомендуется, вы можете заставить dataclass() создать метод __hash__() с помощью unsafe_hash=True.Это может быть в том случае, если ваш класс логически неизменен, но, тем не менее, может быть видоизменен.Это специализированный вариант использования, и его следует тщательно рассмотреть.

Вот правила, управляющие неявным созданием метода __hash__().Обратите внимание, что вы не можете одновременно иметь явный метод __hash__() в своем классе данных и установить unsafe_hash=True;это приведет к TypeError.

Если eq и frozen оба имеют значение true, по умолчанию dataclass() сгенерирует метод __hash__() для вас.Если eq равно true, а замораживается как false, для __hash__() будет установлено значение None, помечая его как не подлежащее обработке (что является изменяемым).Если eq равно false, __hash__() останется без изменений, что означает, что будет использован метод __hash__() суперкласса (если суперкласс является объектом, это означает, что он вернется к хешированию на основе идентификаторов).

...