@@ -109,6 +109,7 @@ class Hashability(enum.Enum):
109109 """
110110
111111 HASHABLE = "hashable" # write a __hash__
112+ HASHABLE_CACHED = "hashable_cache" # write a __hash__ and cache the hash
112113 UNHASHABLE = "unhashable" # set __hash__ to None
113114 LEAVE_ALONE = "leave_alone" # don't touch __hash__
114115
@@ -142,13 +143,19 @@ class ClassProps(NamedTuple):
142143 eq : bool
143144 order : bool
144145 hash : Hashability
145- cache_hash : bool
146146 match_args : bool
147147 str : bool
148148 getstate_setstate : bool
149149 on_setattr : Callable [[str , Any ], Any ]
150150 field_transformer : Callable [[Attribute ], Attribute ]
151151
152+ @property
153+ def is_hashable (self ):
154+ return (
155+ self .hash is Hashability .HASHABLE
156+ or self .hash is Hashability .HASHABLE_CACHED
157+ )
158+
152159
153160def attrib (
154161 default = NOTHING ,
@@ -730,7 +737,7 @@ def __init__(
730737 self ._slots = props .is_slotted
731738 self ._frozen = props .is_frozen
732739 self ._weakref_slot = props .has_weakref_slot
733- self ._cache_hash = props .cache_hash
740+ self ._cache_hash = props .hash is Hashability . HASHABLE_CACHED
734741 self ._has_pre_init = bool (getattr (cls , "__attrs_pre_init__" , False ))
735742 self ._pre_init_has_args = False
736743 if self ._has_pre_init :
@@ -1490,14 +1497,22 @@ def wrap(cls):
14901497 if is_exc :
14911498 hashability = Hashability .LEAVE_ALONE
14921499 elif hash is True :
1493- hashability = Hashability .HASHABLE
1500+ hashability = (
1501+ Hashability .HASHABLE_CACHED
1502+ if cache_hash
1503+ else Hashability .HASHABLE
1504+ )
14941505 elif hash is False :
14951506 hashability = Hashability .LEAVE_ALONE
14961507 elif hash is None :
14971508 if auto_detect is True and _has_own_attribute (cls , "__hash__" ):
14981509 hashability = Hashability .LEAVE_ALONE
14991510 elif eq is True and is_frozen is True :
1500- hashability = Hashability .HASHABLE
1511+ hashability = (
1512+ Hashability .HASHABLE_CACHED
1513+ if cache_hash
1514+ else Hashability .HASHABLE
1515+ )
15011516 elif eq is False :
15021517 hashability = Hashability .LEAVE_ALONE
15031518 else :
@@ -1506,12 +1521,8 @@ def wrap(cls):
15061521 msg = "Invalid value for hash. Must be True, False, or None."
15071522 raise TypeError (msg )
15081523
1509- if hashability is not Hashability .HASHABLE and cache_hash :
1510- msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1511- raise TypeError (msg )
1512-
15131524 if kw_only :
1514- kwo = KeywordOnly .YES if not force_kw_only else KeywordOnly .FORCE
1525+ kwo = KeywordOnly .FORCE if force_kw_only else KeywordOnly .YES
15151526 else :
15161527 kwo = KeywordOnly .NO
15171528
@@ -1538,7 +1549,6 @@ def wrap(cls):
15381549 match_args = match_args ,
15391550 kw_only = kwo ,
15401551 has_weakref_slot = weakref_slot ,
1541- cache_hash = cache_hash ,
15421552 str = str ,
15431553 getstate_setstate = _determine_whether_to_implement (
15441554 cls ,
@@ -1551,6 +1561,10 @@ def wrap(cls):
15511561 field_transformer = field_transformer ,
15521562 )
15531563
1564+ if not props .is_hashable and cache_hash :
1565+ msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1566+ raise TypeError (msg )
1567+
15541568 builder = _ClassBuilder (
15551569 cls ,
15561570 these ,
@@ -1573,7 +1587,7 @@ def wrap(cls):
15731587 if not frozen :
15741588 builder .add_setattr ()
15751589
1576- if props .hash is Hashability . HASHABLE :
1590+ if props .is_hashable :
15771591 builder .add_hash ()
15781592 elif props .hash is Hashability .UNHASHABLE :
15791593 builder .make_unhashable ()
0 commit comments