@@ -103,6 +103,44 @@ def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008
103103 return _none_constructor , _args
104104
105105
106+ class _Hashability (enum .Enum ):
107+ """
108+ The hashability of a class.
109+ """
110+
111+ HASHABLE = "hashable" # write a __hash__
112+ UNHASHABLE = "unhashable" # set __hash__ to None
113+ LEAVE_ALONE = "leave_alone" # don't touch __hash__
114+
115+
116+ class _AttrsParams (NamedTuple ):
117+ """
118+ Effective parameters to attrs() or define() decorators.
119+ """
120+
121+ exception : bool
122+ slots : bool
123+ frozen : bool
124+ init : bool
125+ repr : bool
126+ eq : bool
127+ order : bool
128+ hash : _Hashability
129+ match_args : bool
130+ kw_only : bool
131+ weakref_slot : bool
132+ auto_attribs : bool
133+ collect_by_mro : bool
134+ auto_detect : bool
135+ auto_exc : bool
136+ cache_hash : bool
137+ str : bool
138+ getstate_setstate : bool
139+ has_custom_setattr : bool
140+ on_setattr : Callable [[str , Any ], Any ]
141+ field_transformer : Callable [[Attribute ], Attribute ]
142+
143+
106144def attrib (
107145 default = NOTHING ,
108146 validator = None ,
@@ -663,38 +701,27 @@ def __init__(
663701 self ,
664702 cls : type ,
665703 these ,
666- slots ,
667- frozen ,
668- weakref_slot ,
669- getstate_setstate ,
670- auto_attribs ,
671- kw_only ,
672- cache_hash ,
673- is_exc ,
674- collect_by_mro ,
675- on_setattr ,
676- has_custom_setattr ,
677- field_transformer ,
704+ attrs_params : _AttrsParams ,
678705 ):
679706 attrs , base_attrs , base_map = _transform_attrs (
680707 cls ,
681708 these ,
682- auto_attribs ,
683- kw_only ,
684- collect_by_mro ,
685- field_transformer ,
709+ attrs_params . auto_attribs ,
710+ attrs_params . kw_only ,
711+ attrs_params . collect_by_mro ,
712+ attrs_params . field_transformer ,
686713 )
687714
688715 self ._cls = cls
689- self ._cls_dict = dict (cls .__dict__ ) if slots else {}
716+ self ._cls_dict = dict (cls .__dict__ ) if attrs_params . slots else {}
690717 self ._attrs = attrs
691718 self ._base_names = {a .name for a in base_attrs }
692719 self ._base_attr_map = base_map
693720 self ._attr_names = tuple (a .name for a in attrs )
694- self ._slots = slots
695- self ._frozen = frozen
696- self ._weakref_slot = weakref_slot
697- self ._cache_hash = cache_hash
721+ self ._slots = attrs_params . slots
722+ self ._frozen = attrs_params . frozen
723+ self ._weakref_slot = attrs_params . weakref_slot
724+ self ._cache_hash = attrs_params . cache_hash
698725 self ._has_pre_init = bool (getattr (cls , "__attrs_pre_init__" , False ))
699726 self ._pre_init_has_args = False
700727 if self ._has_pre_init :
@@ -705,20 +732,21 @@ def __init__(
705732 self ._pre_init_has_args = len (pre_init_signature .parameters ) > 1
706733 self ._has_post_init = bool (getattr (cls , "__attrs_post_init__" , False ))
707734 self ._delete_attribs = not bool (these )
708- self ._is_exc = is_exc
709- self ._on_setattr = on_setattr
735+ self ._is_exc = attrs_params . exception
736+ self ._on_setattr = attrs_params . on_setattr
710737
711- self ._has_custom_setattr = has_custom_setattr
738+ self ._has_custom_setattr = attrs_params . has_custom_setattr
712739 self ._wrote_own_setattr = False
713740
714741 self ._cls_dict ["__attrs_attrs__" ] = self ._attrs
742+ self ._cls_dict ["__attrs_params__" ] = attrs_params
715743
716- if frozen :
744+ if attrs_params . frozen :
717745 self ._cls_dict ["__setattr__" ] = _frozen_setattrs
718746 self ._cls_dict ["__delattr__" ] = _frozen_delattrs
719747
720748 self ._wrote_own_setattr = True
721- elif on_setattr in (
749+ elif self . _on_setattr in (
722750 _DEFAULT_ON_SETATTR ,
723751 setters .validate ,
724752 setters .convert ,
@@ -734,18 +762,18 @@ def __init__(
734762 break
735763 if (
736764 (
737- on_setattr == _DEFAULT_ON_SETATTR
765+ self . _on_setattr == _DEFAULT_ON_SETATTR
738766 and not (has_validator or has_converter )
739767 )
740- or (on_setattr == setters .validate and not has_validator )
741- or (on_setattr == setters .convert and not has_converter )
768+ or (self . _on_setattr == setters .validate and not has_validator )
769+ or (self . _on_setattr == setters .convert and not has_converter )
742770 ):
743771 # If class-level on_setattr is set to convert + validate, but
744772 # there's no field to convert or validate, pretend like there's
745773 # no on_setattr.
746774 self ._on_setattr = None
747775
748- if getstate_setstate :
776+ if attrs_params . getstate_setstate :
749777 (
750778 self ._cls_dict ["__getstate__" ],
751779 self ._cls_dict ["__setstate__" ],
@@ -1439,6 +1467,7 @@ def attrs(
14391467 on_setattr = setters .pipe (* on_setattr )
14401468
14411469 def wrap (cls ):
1470+ nonlocal hash
14421471 is_frozen = frozen or _has_frozen_base_class (cls )
14431472 is_exc = auto_exc is True and issubclass (cls , BaseException )
14441473 has_own_setattr = auto_detect and _has_own_attribute (
@@ -1449,84 +1478,95 @@ def wrap(cls):
14491478 msg = "Can't freeze a class with a custom __setattr__."
14501479 raise ValueError (msg )
14511480
1452- builder = _ClassBuilder (
1453- cls ,
1454- these ,
1455- slots ,
1456- is_frozen ,
1457- weakref_slot ,
1458- _determine_whether_to_implement (
1481+ eq = not is_exc and _determine_whether_to_implement (
1482+ cls , eq_ , auto_detect , ("__eq__" , "__ne__" )
1483+ )
1484+
1485+ if is_exc :
1486+ hashability = _Hashability .LEAVE_ALONE
1487+ elif hash is True :
1488+ hashability = _Hashability .HASHABLE
1489+ elif hash is False :
1490+ hashability = _Hashability .LEAVE_ALONE
1491+ elif hash is None :
1492+ if auto_detect is True and _has_own_attribute (cls , "__hash__" ):
1493+ hashability = _Hashability .LEAVE_ALONE
1494+ elif eq is True and is_frozen is True :
1495+ hashability = _Hashability .HASHABLE
1496+ elif eq is False :
1497+ hashability = _Hashability .LEAVE_ALONE
1498+ else :
1499+ hashability = _Hashability .UNHASHABLE
1500+ else :
1501+ msg = "Invalid value for hash. Must be True, False, or None."
1502+ raise TypeError (msg )
1503+
1504+ if hashability is not _Hashability .HASHABLE and cache_hash :
1505+ msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1506+ raise TypeError (msg )
1507+
1508+ attrs_params = _AttrsParams (
1509+ exception = is_exc ,
1510+ frozen = is_frozen ,
1511+ slots = slots ,
1512+ init = _determine_whether_to_implement (
1513+ cls , init , auto_detect , ("__init__" ,)
1514+ ),
1515+ repr = _determine_whether_to_implement (
1516+ cls , repr , auto_detect , ("__repr__" ,)
1517+ ),
1518+ eq = eq ,
1519+ order = not is_exc
1520+ and _determine_whether_to_implement (
1521+ cls ,
1522+ order_ ,
1523+ auto_detect ,
1524+ ("__lt__" , "__le__" , "__gt__" , "__ge__" ),
1525+ ),
1526+ hash = hashability ,
1527+ match_args = match_args ,
1528+ kw_only = kw_only ,
1529+ weakref_slot = weakref_slot ,
1530+ auto_attribs = auto_attribs if auto_attribs is not None else False ,
1531+ collect_by_mro = collect_by_mro ,
1532+ auto_detect = auto_detect ,
1533+ auto_exc = is_exc ,
1534+ cache_hash = cache_hash ,
1535+ str = str ,
1536+ getstate_setstate = _determine_whether_to_implement (
14591537 cls ,
14601538 getstate_setstate ,
14611539 auto_detect ,
14621540 ("__getstate__" , "__setstate__" ),
14631541 default = slots ,
14641542 ),
1465- auto_attribs ,
1466- kw_only ,
1467- cache_hash ,
1468- is_exc ,
1469- collect_by_mro ,
1470- on_setattr ,
1471- has_own_setattr ,
1472- field_transformer ,
1543+ has_custom_setattr = has_own_setattr ,
1544+ on_setattr = on_setattr ,
1545+ field_transformer = field_transformer ,
14731546 )
14741547
1475- if _determine_whether_to_implement (
1476- cls , repr , auto_detect , ( "__repr__" ,)
1477- ) :
1548+ builder = _ClassBuilder ( cls , these , attrs_params )
1549+
1550+ if attrs_params . repr is True :
14781551 builder .add_repr (repr_ns )
14791552
1480- if str is True :
1553+ if attrs_params . str is True :
14811554 builder .add_str ()
14821555
1483- eq = _determine_whether_to_implement (
1484- cls , eq_ , auto_detect , ("__eq__" , "__ne__" )
1485- )
1486- if not is_exc and eq is True :
1556+ if attrs_params .eq is True :
14871557 builder .add_eq ()
1488- if not is_exc and _determine_whether_to_implement (
1489- cls , order_ , auto_detect , ("__lt__" , "__le__" , "__gt__" , "__ge__" )
1490- ):
1558+ if attrs_params .order is True :
14911559 builder .add_order ()
14921560
14931561 if not frozen :
14941562 builder .add_setattr ()
14951563
1496- nonlocal hash
1497- if (
1498- hash is None
1499- and auto_detect is True
1500- and _has_own_attribute (cls , "__hash__" )
1501- ):
1502- hash = False
1503-
1504- if hash is not True and hash is not False and hash is not None :
1505- # Can't use `hash in` because 1 == True for example.
1506- msg = "Invalid value for hash. Must be True, False, or None."
1507- raise TypeError (msg )
1508-
1509- if hash is False or (hash is None and eq is False ) or is_exc :
1510- # Don't do anything. Should fall back to __object__'s __hash__
1511- # which is by id.
1512- if cache_hash :
1513- msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1514- raise TypeError (msg )
1515- elif hash is True or (
1516- hash is None and eq is True and is_frozen is True
1517- ):
1518- # Build a __hash__ if told so, or if it's safe.
1564+ if attrs_params .hash is _Hashability .HASHABLE :
15191565 builder .add_hash ()
1520- else :
1521- # Raise TypeError on attempts to hash.
1522- if cache_hash :
1523- msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1524- raise TypeError (msg )
1566+ elif attrs_params .hash is _Hashability .UNHASHABLE :
15251567 builder .make_unhashable ()
15261568
1527- if _determine_whether_to_implement (
1528- cls , init , auto_detect , ("__init__" ,)
1529- ):
1569+ if attrs_params .init :
15301570 builder .add_init ()
15311571 else :
15321572 builder .add_attrs_init ()
0 commit comments