@@ -103,6 +103,45 @@ 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+ force_kw_only : bool
132+ weakref_slot : bool
133+ auto_attribs : bool
134+ collect_by_mro : bool
135+ auto_detect : bool
136+ auto_exc : bool
137+ cache_hash : bool
138+ str : bool
139+ getstate_setstate : bool
140+ has_custom_setattr : bool
141+ on_setattr : Callable [[str , Any ], Any ]
142+ field_transformer : Callable [[Attribute ], Attribute ]
143+
144+
106145def attrib (
107146 default = NOTHING ,
108147 validator = None ,
@@ -673,40 +712,28 @@ def __init__(
673712 self ,
674713 cls : type ,
675714 these ,
676- slots ,
677- frozen ,
678- weakref_slot ,
679- getstate_setstate ,
680- auto_attribs ,
681- kw_only ,
682- force_kw_only ,
683- cache_hash ,
684- is_exc ,
685- collect_by_mro ,
686- on_setattr ,
687- has_custom_setattr ,
688- field_transformer ,
715+ attrs_params : _AttrsParams ,
689716 ):
690717 attrs , base_attrs , base_map = _transform_attrs (
691718 cls ,
692719 these ,
693- auto_attribs ,
694- kw_only ,
695- force_kw_only ,
696- collect_by_mro ,
697- field_transformer ,
720+ attrs_params . auto_attribs ,
721+ attrs_params . kw_only ,
722+ attrs_params . force_kw_only ,
723+ attrs_params . collect_by_mro ,
724+ attrs_params . field_transformer ,
698725 )
699726
700727 self ._cls = cls
701- self ._cls_dict = dict (cls .__dict__ ) if slots else {}
728+ self ._cls_dict = dict (cls .__dict__ ) if attrs_params . slots else {}
702729 self ._attrs = attrs
703730 self ._base_names = {a .name for a in base_attrs }
704731 self ._base_attr_map = base_map
705732 self ._attr_names = tuple (a .name for a in attrs )
706- self ._slots = slots
707- self ._frozen = frozen
708- self ._weakref_slot = weakref_slot
709- self ._cache_hash = cache_hash
733+ self ._slots = attrs_params . slots
734+ self ._frozen = attrs_params . frozen
735+ self ._weakref_slot = attrs_params . weakref_slot
736+ self ._cache_hash = attrs_params . cache_hash
710737 self ._has_pre_init = bool (getattr (cls , "__attrs_pre_init__" , False ))
711738 self ._pre_init_has_args = False
712739 if self ._has_pre_init :
@@ -717,20 +744,21 @@ def __init__(
717744 self ._pre_init_has_args = len (pre_init_signature .parameters ) > 1
718745 self ._has_post_init = bool (getattr (cls , "__attrs_post_init__" , False ))
719746 self ._delete_attribs = not bool (these )
720- self ._is_exc = is_exc
721- self ._on_setattr = on_setattr
747+ self ._is_exc = attrs_params . exception
748+ self ._on_setattr = attrs_params . on_setattr
722749
723- self ._has_custom_setattr = has_custom_setattr
750+ self ._has_custom_setattr = attrs_params . has_custom_setattr
724751 self ._wrote_own_setattr = False
725752
726753 self ._cls_dict ["__attrs_attrs__" ] = self ._attrs
754+ self ._cls_dict ["__attrs_params__" ] = attrs_params
727755
728- if frozen :
756+ if attrs_params . frozen :
729757 self ._cls_dict ["__setattr__" ] = _frozen_setattrs
730758 self ._cls_dict ["__delattr__" ] = _frozen_delattrs
731759
732760 self ._wrote_own_setattr = True
733- elif on_setattr in (
761+ elif self . _on_setattr in (
734762 _DEFAULT_ON_SETATTR ,
735763 setters .validate ,
736764 setters .convert ,
@@ -746,18 +774,18 @@ def __init__(
746774 break
747775 if (
748776 (
749- on_setattr == _DEFAULT_ON_SETATTR
777+ self . _on_setattr == _DEFAULT_ON_SETATTR
750778 and not (has_validator or has_converter )
751779 )
752- or (on_setattr == setters .validate and not has_validator )
753- or (on_setattr == setters .convert and not has_converter )
780+ or (self . _on_setattr == setters .validate and not has_validator )
781+ or (self . _on_setattr == setters .convert and not has_converter )
754782 ):
755783 # If class-level on_setattr is set to convert + validate, but
756784 # there's no field to convert or validate, pretend like there's
757785 # no on_setattr.
758786 self ._on_setattr = None
759787
760- if getstate_setstate :
788+ if attrs_params . getstate_setstate :
761789 (
762790 self ._cls_dict ["__getstate__" ],
763791 self ._cls_dict ["__setstate__" ],
@@ -1456,6 +1484,7 @@ def attrs(
14561484 on_setattr = setters .pipe (* on_setattr )
14571485
14581486 def wrap (cls ):
1487+ nonlocal hash
14591488 is_frozen = frozen or _has_frozen_base_class (cls )
14601489 is_exc = auto_exc is True and issubclass (cls , BaseException )
14611490 has_own_setattr = auto_detect and _has_own_attribute (
@@ -1466,85 +1495,96 @@ def wrap(cls):
14661495 msg = "Can't freeze a class with a custom __setattr__."
14671496 raise ValueError (msg )
14681497
1469- builder = _ClassBuilder (
1470- cls ,
1471- these ,
1472- slots ,
1473- is_frozen ,
1474- weakref_slot ,
1475- _determine_whether_to_implement (
1498+ eq = not is_exc and _determine_whether_to_implement (
1499+ cls , eq_ , auto_detect , ("__eq__" , "__ne__" )
1500+ )
1501+
1502+ if is_exc :
1503+ hashability = _Hashability .LEAVE_ALONE
1504+ elif hash is True :
1505+ hashability = _Hashability .HASHABLE
1506+ elif hash is False :
1507+ hashability = _Hashability .LEAVE_ALONE
1508+ elif hash is None :
1509+ if auto_detect is True and _has_own_attribute (cls , "__hash__" ):
1510+ hashability = _Hashability .LEAVE_ALONE
1511+ elif eq is True and is_frozen is True :
1512+ hashability = _Hashability .HASHABLE
1513+ elif eq is False :
1514+ hashability = _Hashability .LEAVE_ALONE
1515+ else :
1516+ hashability = _Hashability .UNHASHABLE
1517+ else :
1518+ msg = "Invalid value for hash. Must be True, False, or None."
1519+ raise TypeError (msg )
1520+
1521+ if hashability is not _Hashability .HASHABLE and cache_hash :
1522+ msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1523+ raise TypeError (msg )
1524+
1525+ attrs_params = _AttrsParams (
1526+ exception = is_exc ,
1527+ frozen = is_frozen ,
1528+ slots = slots ,
1529+ init = _determine_whether_to_implement (
1530+ cls , init , auto_detect , ("__init__" ,)
1531+ ),
1532+ repr = _determine_whether_to_implement (
1533+ cls , repr , auto_detect , ("__repr__" ,)
1534+ ),
1535+ eq = eq ,
1536+ order = not is_exc
1537+ and _determine_whether_to_implement (
1538+ cls ,
1539+ order_ ,
1540+ auto_detect ,
1541+ ("__lt__" , "__le__" , "__gt__" , "__ge__" ),
1542+ ),
1543+ hash = hashability ,
1544+ match_args = match_args ,
1545+ kw_only = kw_only ,
1546+ force_kw_only = force_kw_only ,
1547+ weakref_slot = weakref_slot ,
1548+ auto_attribs = auto_attribs if auto_attribs is not None else False ,
1549+ collect_by_mro = collect_by_mro ,
1550+ auto_detect = auto_detect ,
1551+ auto_exc = is_exc ,
1552+ cache_hash = cache_hash ,
1553+ str = str ,
1554+ getstate_setstate = _determine_whether_to_implement (
14761555 cls ,
14771556 getstate_setstate ,
14781557 auto_detect ,
14791558 ("__getstate__" , "__setstate__" ),
14801559 default = slots ,
14811560 ),
1482- auto_attribs ,
1483- kw_only ,
1484- force_kw_only ,
1485- cache_hash ,
1486- is_exc ,
1487- collect_by_mro ,
1488- on_setattr ,
1489- has_own_setattr ,
1490- field_transformer ,
1561+ has_custom_setattr = has_own_setattr ,
1562+ on_setattr = on_setattr ,
1563+ field_transformer = field_transformer ,
14911564 )
14921565
1493- if _determine_whether_to_implement (
1494- cls , repr , auto_detect , ( "__repr__" ,)
1495- ) :
1566+ builder = _ClassBuilder ( cls , these , attrs_params )
1567+
1568+ if attrs_params . repr is True :
14961569 builder .add_repr (repr_ns )
14971570
1498- if str is True :
1571+ if attrs_params . str is True :
14991572 builder .add_str ()
15001573
1501- eq = _determine_whether_to_implement (
1502- cls , eq_ , auto_detect , ("__eq__" , "__ne__" )
1503- )
1504- if not is_exc and eq is True :
1574+ if attrs_params .eq is True :
15051575 builder .add_eq ()
1506- if not is_exc and _determine_whether_to_implement (
1507- cls , order_ , auto_detect , ("__lt__" , "__le__" , "__gt__" , "__ge__" )
1508- ):
1576+ if attrs_params .order is True :
15091577 builder .add_order ()
15101578
15111579 if not frozen :
15121580 builder .add_setattr ()
15131581
1514- nonlocal hash
1515- if (
1516- hash is None
1517- and auto_detect is True
1518- and _has_own_attribute (cls , "__hash__" )
1519- ):
1520- hash = False
1521-
1522- if hash is not True and hash is not False and hash is not None :
1523- # Can't use `hash in` because 1 == True for example.
1524- msg = "Invalid value for hash. Must be True, False, or None."
1525- raise TypeError (msg )
1526-
1527- if hash is False or (hash is None and eq is False ) or is_exc :
1528- # Don't do anything. Should fall back to __object__'s __hash__
1529- # which is by id.
1530- if cache_hash :
1531- msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1532- raise TypeError (msg )
1533- elif hash is True or (
1534- hash is None and eq is True and is_frozen is True
1535- ):
1536- # Build a __hash__ if told so, or if it's safe.
1582+ if attrs_params .hash is _Hashability .HASHABLE :
15371583 builder .add_hash ()
1538- else :
1539- # Raise TypeError on attempts to hash.
1540- if cache_hash :
1541- msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
1542- raise TypeError (msg )
1584+ elif attrs_params .hash is _Hashability .UNHASHABLE :
15431585 builder .make_unhashable ()
15441586
1545- if _determine_whether_to_implement (
1546- cls , init , auto_detect , ("__init__" ,)
1547- ):
1587+ if attrs_params .init :
15481588 builder .add_init ()
15491589 else :
15501590 builder .add_attrs_init ()
0 commit comments