@@ -109,6 +109,7 @@ class Hashability(enum.Enum):
109
109
"""
110
110
111
111
HASHABLE = "hashable" # write a __hash__
112
+ HASHABLE_CACHED = "hashable_cache" # write a __hash__ and cache the hash
112
113
UNHASHABLE = "unhashable" # set __hash__ to None
113
114
LEAVE_ALONE = "leave_alone" # don't touch __hash__
114
115
@@ -142,13 +143,19 @@ class ClassProps(NamedTuple):
142
143
eq : bool
143
144
order : bool
144
145
hash : Hashability
145
- cache_hash : bool
146
146
match_args : bool
147
147
str : bool
148
148
getstate_setstate : bool
149
149
on_setattr : Callable [[str , Any ], Any ]
150
150
field_transformer : Callable [[Attribute ], Attribute ]
151
151
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
+
152
159
153
160
def attrib (
154
161
default = NOTHING ,
@@ -730,7 +737,7 @@ def __init__(
730
737
self ._slots = props .is_slotted
731
738
self ._frozen = props .is_frozen
732
739
self ._weakref_slot = props .has_weakref_slot
733
- self ._cache_hash = props .cache_hash
740
+ self ._cache_hash = props .hash is Hashability . HASHABLE_CACHED
734
741
self ._has_pre_init = bool (getattr (cls , "__attrs_pre_init__" , False ))
735
742
self ._pre_init_has_args = False
736
743
if self ._has_pre_init :
@@ -1490,14 +1497,22 @@ def wrap(cls):
1490
1497
if is_exc :
1491
1498
hashability = Hashability .LEAVE_ALONE
1492
1499
elif hash is True :
1493
- hashability = Hashability .HASHABLE
1500
+ hashability = (
1501
+ Hashability .HASHABLE_CACHED
1502
+ if cache_hash
1503
+ else Hashability .HASHABLE
1504
+ )
1494
1505
elif hash is False :
1495
1506
hashability = Hashability .LEAVE_ALONE
1496
1507
elif hash is None :
1497
1508
if auto_detect is True and _has_own_attribute (cls , "__hash__" ):
1498
1509
hashability = Hashability .LEAVE_ALONE
1499
1510
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
+ )
1501
1516
elif eq is False :
1502
1517
hashability = Hashability .LEAVE_ALONE
1503
1518
else :
@@ -1506,12 +1521,8 @@ def wrap(cls):
1506
1521
msg = "Invalid value for hash. Must be True, False, or None."
1507
1522
raise TypeError (msg )
1508
1523
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
-
1513
1524
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
1515
1526
else :
1516
1527
kwo = KeywordOnly .NO
1517
1528
@@ -1538,7 +1549,6 @@ def wrap(cls):
1538
1549
match_args = match_args ,
1539
1550
kw_only = kwo ,
1540
1551
has_weakref_slot = weakref_slot ,
1541
- cache_hash = cache_hash ,
1542
1552
str = str ,
1543
1553
getstate_setstate = _determine_whether_to_implement (
1544
1554
cls ,
@@ -1551,6 +1561,10 @@ def wrap(cls):
1551
1561
field_transformer = field_transformer ,
1552
1562
)
1553
1563
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
+
1554
1568
builder = _ClassBuilder (
1555
1569
cls ,
1556
1570
these ,
@@ -1573,7 +1587,7 @@ def wrap(cls):
1573
1587
if not frozen :
1574
1588
builder .add_setattr ()
1575
1589
1576
- if props .hash is Hashability . HASHABLE :
1590
+ if props .is_hashable :
1577
1591
builder .add_hash ()
1578
1592
elif props .hash is Hashability .UNHASHABLE :
1579
1593
builder .make_unhashable ()
0 commit comments