Disons que j'ai une classe de données en python3. Je veux pouvoir hacher et ordonner ces objets.
Je veux seulement qu'ils soient commandés/hachés sur id.
Je vois dans les documents que je peux simplement implémenter __hash__ et tout ça, mais j'aimerais que datacalsses fasse le travail pour moi car ils sont destinés à gérer cela.
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'
De les docs :
Voici les règles régissant la création implicite d'une méthode
__hash__()
:[...]
Si
eq
etfrozen
sont tous les deux vrais, par défautdataclass()
générera pour vous une méthode__hash__()
. Sieq
est vrai etfrozen
est faux,__hash__()
sera défini surNone
, le marquant comme non-lavable (ce qu'il est, car il est modifiable) . Sieq
est faux,__hash__()
restera intacte, ce qui signifie que la méthode__hash__()
de la superclasse sera utilisée (si la superclasse est un objet, cela signifie qu'elle retombera au hachage basé sur l'ID).
Depuis que vous définissez eq=True
et laissé frozen
par défaut (False
), votre classe de données n'est pas partageable.
Vous avez 3 options:
frozen=True
(en plus de eq=True
), ce qui rendra votre classe immuable et lavable.Ensemble unsafe_hash=True
, ce qui créera un __hash__
mais laissez votre classe modifiable, ce qui risque de poser des problèmes si une instance de votre classe est modifiée alors qu'elle est stockée dans un dict ou un ensemble:
cat = Category('foo', 'bar')
categories = {cat}
cat.id = 'baz'
print(cat in categories) # False
__hash__
méthode.TL; DR
Utilisez frozen=True
Conjointement avec eq=True
(Ce qui rendra les instances immuables).
Réponse longue
De la docs :
__hash__()
est utilisé par la fonction intégréehash()
, et lorsque des objets sont ajoutés à des collections hachées telles que des dictionnaires et des ensembles. Avoir une__hash__()
implique que les instances de la classe sont immuables. La mutabilité est une propriété compliquée qui dépend de l'intention du programmeur, de l'existence et du comportement de__eq__()
, et des valeurs de l'égaliseur et des drapeaux figés dans le décorateurdataclass()
.Par défaut,
dataclass()
n'ajoutera pas implicitement une méthode__hash__()
sauf si cela est sûr. Il n'ajoutera ni ne modifiera pas non plus une méthode__hash__()
existante explicitement définie. La définition de l'attribut de classe__hash__ = None
A une signification spécifique pour Python, comme décrit dans la documentation__hash__()
.Si
__hash__()
n'est pas explicitement défini, ou s'il est défini sur None, alorsdataclass()
peut ajouter une méthode implicite__hash__()
. Bien que cela ne soit pas recommandé, vous pouvez forcerdataclass()
pour créer une méthode__hash__()
avecunsafe_hash=True
. Cela peut être le cas si votre classe est logiquement immuable mais peut néanmoins être mutée. Il s'agit d'un cas d'utilisation spécialisé et doit être soigneusement étudié.Voici les règles régissant la création implicite d'une méthode
__hash__()
. Notez que vous ne pouvez pas avoir à la fois une méthode__hash__()
explicite dans votre classe de données et définirunsafe_hash=True
; cela se traduira par unTypeError
.Si eq et figé sont tous deux vrais, par défaut
dataclass()
générera une méthode__hash__()
pour vous. Si eq est vrai et figé est faux,__hash__()
sera défini sur Aucun, le marquant comme non-lavable (ce qui est le cas, car il est mutable). Si eq est faux,__hash__()
restera intacte, ce qui signifie que la méthode__hash__()
de la superclasse sera utilisée (si la superclasse est objet, cela signifie qu'elle retombera sur le hachage basé sur l'id ).
Je voudrais ajouter une note spéciale pour l'utilisation de unsafe_hash.
Vous pouvez exclure les champs de la comparaison par hachage en définissant compare = False ou hash = False. (le hachage hérite par défaut de compare).
Cela peut être utile si vous stockez des nœuds dans un graphique mais que vous souhaitez les marquer comme visités sans casser leur hachage (par exemple s'ils se trouvent dans un ensemble de nœuds non visités ..).
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.")
Cela m'a pris heures pour comprendre ... D'autres lectures utiles que j'ai trouvées sont le doc python sur les classes de données. Spécifiquement voir la documentation du champ et les documentations d'argument de la classe de données. https://docs.python.org/3/library/dataclasses.html