@@ -218,7 +218,7 @@ def attrib(
218
218
.. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
219
219
.. versionadded:: 19.2.0 *eq* and *order*
220
220
"""
221
- eq , order = _determine_eq_order (cmp , eq , order )
221
+ eq , order = _determine_eq_order (cmp , eq , order , True )
222
222
223
223
if hash is not None and hash is not True and hash is not False :
224
224
raise TypeError (
@@ -308,20 +308,32 @@ def _is_class_var(annot):
308
308
return str (annot ).startswith (_classvar_prefixes )
309
309
310
310
311
- def _get_annotations (cls ):
311
+ def _has_own_attribute (cls , attrib_name ):
312
312
"""
313
- Get annotations for *cls*.
313
+ Check whether *cls* defines *attrib_name* (and doesn't just inherit it).
314
+
315
+ Requires Python 3.
314
316
"""
315
- anns = getattr (cls , "__annotations__" , None )
316
- if anns is None :
317
- return {}
317
+ attr = getattr (cls , attrib_name , _sentinel )
318
+ if attr is _sentinel :
319
+ return False
318
320
319
- # Verify that the annotations aren't merely inherited.
320
321
for base_cls in cls .__mro__ [1 :]:
321
- if anns is getattr (base_cls , "__annotations__" , None ):
322
- return {}
322
+ a = getattr (base_cls , attrib_name , None )
323
+ if attr is a :
324
+ return False
325
+
326
+ return True
327
+
328
+
329
+ def _get_annotations (cls ):
330
+ """
331
+ Get annotations for *cls*.
332
+ """
333
+ if _has_own_attribute (cls , "__annotations__" ):
334
+ return cls .__annotations__
323
335
324
- return anns
336
+ return {}
325
337
326
338
327
339
def _counter_getter (e ):
@@ -754,10 +766,10 @@ def _add_method_dunders(self, method):
754
766
)
755
767
756
768
757
- def _determine_eq_order (cmp , eq , order ):
769
+ def _determine_eq_order (cmp , eq , order , default_eq ):
758
770
"""
759
771
Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
760
- values of eq and order.
772
+ values of eq and order. If *eq* is None, set it to *default_eq*.
761
773
"""
762
774
if cmp is not None and any ((eq is not None , order is not None )):
763
775
raise ValueError ("Don't mix `cmp` with `eq' and `order`." )
@@ -768,9 +780,10 @@ def _determine_eq_order(cmp, eq, order):
768
780
769
781
return cmp , cmp
770
782
771
- # If left None, equality is on and ordering mirrors equality.
783
+ # If left None, equality is set to the specified default and ordering
784
+ # mirrors equality.
772
785
if eq is None :
773
- eq = True
786
+ eq = default_eq
774
787
775
788
if order is None :
776
789
order = eq
@@ -781,14 +794,38 @@ def _determine_eq_order(cmp, eq, order):
781
794
return eq , order
782
795
783
796
797
+ def _determine_whether_to_implement (cls , flag , auto_detect , dunders ):
798
+ """
799
+ Check whether we should implement a set of methods for *cls*.
800
+
801
+ *flag* is the argument passed into @attr.s like 'init', *auto_detect* the
802
+ same as passed into @attr.s and *dunders* is a tuple of attribute names
803
+ whose presence signal that the user has implemented it themselves.
804
+
805
+ auto_detect must be False on Python 2.
806
+ """
807
+ if flag is True or flag is None and auto_detect is False :
808
+ return True
809
+
810
+ if flag is False :
811
+ return False
812
+
813
+ # Logically, flag is None and auto_detect is True here.
814
+ for dunder in dunders :
815
+ if _has_own_attribute (cls , dunder ):
816
+ return False
817
+
818
+ return True
819
+
820
+
784
821
def attrs (
785
822
maybe_cls = None ,
786
823
these = None ,
787
824
repr_ns = None ,
788
- repr = True ,
825
+ repr = None ,
789
826
cmp = None ,
790
827
hash = None ,
791
- init = True ,
828
+ init = None ,
792
829
slots = False ,
793
830
frozen = False ,
794
831
weakref_slot = True ,
@@ -799,6 +836,7 @@ def attrs(
799
836
auto_exc = False ,
800
837
eq = None ,
801
838
order = None ,
839
+ auto_detect = False ,
802
840
):
803
841
r"""
804
842
A class decorator that adds `dunder
@@ -823,6 +861,22 @@ def attrs(
823
861
:param str repr_ns: When using nested classes, there's no way in Python 2
824
862
to automatically detect that. Therefore it's possible to set the
825
863
namespace explicitly for a more meaningful ``repr`` output.
864
+ :param bool auto_detect: Instead of setting the *init*, *repr*, *eq*,
865
+ *order*, and *hash* arguments explicitly, assume they are set to
866
+ ``True`` **unless any** of the involved methods for one of the
867
+ arguments is implemented in the *current* class (i.e. it is *not*
868
+ inherited from some base class).
869
+
870
+ So for example by implementing ``__eq__`` on a class yourself,
871
+ ``attrs`` will deduce ``eq=False`` and won't create *neither*
872
+ ``__eq__`` *nor* ``__ne__``.
873
+
874
+ Passing ``True`` or ``False`` to *init*, *repr*, *eq*, *order*,
875
+ *cmp*, or *hash* overrides whatever *auto_detect* would determine.
876
+
877
+ *auto_detect* requires Python 3. Setting it ``True`` on Python 2 raises
878
+ a `PythonTooOldError`.
879
+
826
880
:param bool repr: Create a ``__repr__`` method with a human readable
827
881
representation of ``attrs`` attributes..
828
882
:param bool str: Create a ``__str__`` method that is identical to
@@ -891,8 +945,8 @@ def attrs(
891
945
892
946
:param bool weakref_slot: Make instances weak-referenceable. This has no
893
947
effect unless ``slots`` is also enabled.
894
- :param bool auto_attribs: If True, collect `PEP 526`_-annotated attributes
895
- (Python 3.6 and later only) from the class body.
948
+ :param bool auto_attribs: If `` True`` , collect `PEP 526`_-annotated
949
+ attributes (Python 3.6 and later only) from the class body.
896
950
897
951
In this case, you **must** annotate every field. If ``attrs``
898
952
encounters a field that is set to an `attr.ib` but lacks a type
@@ -957,8 +1011,15 @@ def attrs(
957
1011
.. versionadded:: 19.1.0 *auto_exc*
958
1012
.. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
959
1013
.. versionadded:: 19.2.0 *eq* and *order*
1014
+ .. versionadded:: 20.1.0 *auto_detect*
960
1015
"""
961
- eq , order = _determine_eq_order (cmp , eq , order )
1016
+ if auto_detect and PY2 :
1017
+ raise PythonTooOldError (
1018
+ "auto_detect only works on Python 3 and later."
1019
+ )
1020
+
1021
+ eq_ , order_ = _determine_eq_order (cmp , eq , order , None )
1022
+ hash_ = hash # workaround the lack of nonlocal
962
1023
963
1024
def wrap (cls ):
964
1025
@@ -978,16 +1039,31 @@ def wrap(cls):
978
1039
cache_hash ,
979
1040
is_exc ,
980
1041
)
981
-
982
- if repr is True :
1042
+ if _determine_whether_to_implement (
1043
+ cls , repr , auto_detect , ("__repr__" ,)
1044
+ ):
983
1045
builder .add_repr (repr_ns )
984
1046
if str is True :
985
1047
builder .add_str ()
986
- if eq is True and not is_exc :
1048
+
1049
+ eq = _determine_whether_to_implement (
1050
+ cls , eq_ , auto_detect , ("__eq__" , "__ne__" )
1051
+ )
1052
+ if not is_exc and eq is True :
987
1053
builder .add_eq ()
988
- if order is True and not is_exc :
1054
+ if not is_exc and _determine_whether_to_implement (
1055
+ cls , order_ , auto_detect , ("__lt__" , "__le__" , "__gt__" , "__ge__" )
1056
+ ):
989
1057
builder .add_order ()
990
1058
1059
+ if (
1060
+ hash_ is None
1061
+ and auto_detect is True
1062
+ and _has_own_attribute (cls , "__hash__" )
1063
+ ):
1064
+ hash = False
1065
+ else :
1066
+ hash = hash_
991
1067
if hash is not True and hash is not False and hash is not None :
992
1068
# Can't use `hash in` because 1 == True for example.
993
1069
raise TypeError (
@@ -1015,7 +1091,9 @@ def wrap(cls):
1015
1091
)
1016
1092
builder .make_unhashable ()
1017
1093
1018
- if init is True :
1094
+ if _determine_whether_to_implement (
1095
+ cls , init , auto_detect , ("__init__" ,)
1096
+ ):
1019
1097
builder .add_init ()
1020
1098
else :
1021
1099
if cache_hash :
@@ -1832,7 +1910,7 @@ def __init__(
1832
1910
eq = None ,
1833
1911
order = None ,
1834
1912
):
1835
- eq , order = _determine_eq_order (cmp , eq , order )
1913
+ eq , order = _determine_eq_order (cmp , eq , order , True )
1836
1914
1837
1915
# Cache this descriptor here to speed things up later.
1838
1916
bound_setattr = _obj_setattr .__get__ (self , Attribute )
@@ -2178,7 +2256,10 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments):
2178
2256
attributes_arguments ["eq" ],
2179
2257
attributes_arguments ["order" ],
2180
2258
) = _determine_eq_order (
2181
- cmp , attributes_arguments .get ("eq" ), attributes_arguments .get ("order" )
2259
+ cmp ,
2260
+ attributes_arguments .get ("eq" ),
2261
+ attributes_arguments .get ("order" ),
2262
+ True ,
2182
2263
)
2183
2264
2184
2265
return _attrs (these = cls_dict , ** attributes_arguments )(type_ )
0 commit comments