Как сделать размороженный экземпляр класса данных хэшируемым?

При определении класса данных с frozen=False (поведение по умолчанию) и последующем создании экземпляра объекта этого класса есть ли способ сделать этот объект хешируемым?

Зачем мне это нужно?

До Python 3.7 я использовал именованные кортежи вместо классов данных и находил дубликаты с помощью set(), но я больше не могу его использовать. Я не хочу использовать frozen=True в классах данных по другим причинам.


person David D.    schedule 17.04.2019    source источник
comment
какие еще причины? если вы хотите хешировать, вы почти всегда хотите замороженный   -  person wim    schedule 17.04.2019
comment
@wim, потому что в одном контексте мне нужна изменчивость класса данных для изменения атрибутов, а в другом контексте мне нужно сравнить равенство между двумя списками этих объектов. Может быть, я мог бы написать 2 разных класса данных с одинаковыми атрибутами, но один замороженный, а другой нет?   -  person David D.    schedule 17.04.2019
comment
@DavidD.: Похоже, вас может заинтересовать dataclasses.replace .   -  person user2357112 supports Monica    schedule 18.04.2019
comment
Да! Я только что увидел dataclasses.astuple, который тоже может помочь.   -  person David D.    schedule 18.04.2019


Ответы (1)


Для этого есть параметр:

@dataclass(unsafe_hash=True)
class Whatever:
    ...

Однако вам, вероятно, следует использовать frozen=True. Если вам нужно обновить атрибуты в замороженном экземпляре класса данных, вы можете использовать dataclasses.replace для создания нового объекта:

new_thing = dataclasses.replace(old_thing, var=new_val)

Вы также можете использовать изменяемый экземпляр класса данных и dataclasses.astuple для получения кортеж, когда вам нужно что-то хешируемое. Однако будьте осторожны — astuple имеет кучу странного поведения при копировании, поэтому вы можете получить следующее поведение:

In [1]: import dataclasses

In [2]: @dataclasses.dataclass
   ...: class Foo:
   ...:     a: object
   ...:     b: object
   ...:     

In [3]: x = object()

In [4]: a = Foo(x, x)

In [5]: b = dataclasses.astuple(a)

In [6]: b[0] is b[1]
Out[6]: False

In [7]: b[0] is x
Out[7]: False

In [8]: a == a
Out[8]: True

In [9]: dataclasses.astuple(a) == dataclasses.astuple(a)
Out[9]: False
person user2357112 supports Monica    schedule 17.04.2019
comment
Спасибо, звучит очень интересно. Я читал, является ли ваш класс логически неизменным, но, тем не менее, может быть изменен. Это особый вариант использования, и его следует тщательно рассмотреть. Можете ли вы рассказать о небезопасном характере этого? - person David D.; 18.04.2019
comment
@DavidD.: По той же причине, по которой любой другой изменяемый, хэшируемый объект будет небезопасным: мутация разрушает любые наборы, словари или другие структуры данных на основе хэшей, в которых может находиться объект. - person user2357112 supports Monica; 18.04.2019
comment
Вот о чем я думал. Могу ли я попросить вас дополнить свой ответ сравнениями с другими решениями (replace, astuple), чтобы я мог отметить ваш ответ как принятый? Спасибо еще раз. - person David D.; 18.04.2019
comment
@DavidD.: Ответ расширен. - person user2357112 supports Monica; 18.04.2019