@@ -217,7 +217,7 @@ def attrib(
217
217
.. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
218
218
.. versionadded:: 19.2.0 *eq* and *order*
219
219
"""
220
- eq , order = _determine_eq_order (cmp , eq , order )
220
+ eq , order = _determine_eq_order (cmp , eq , order , True )
221
221
222
222
if hash is not None and hash is not True and hash is not False :
223
223
raise TypeError (
@@ -307,20 +307,32 @@ def _is_class_var(annot):
307
307
return str (annot ).startswith (_classvar_prefixes )
308
308
309
309
310
- def _get_annotations (cls ):
310
+ def _has_own_attribute (cls , attrib_name ):
311
311
"""
312
- Get annotations for *cls*.
312
+ Check whether *cls* defines *attrib_name* (and doesn't just inherit it).
313
+
314
+ Requires Python 3.
313
315
"""
314
- anns = getattr (cls , "__annotations__" , None )
315
- if anns is None :
316
- return {}
316
+ attr = getattr (cls , attrib_name , _sentinel )
317
+ if attr is _sentinel :
318
+ return False
317
319
318
- # Verify that the annotations aren't merely inherited.
319
320
for base_cls in cls .__mro__ [1 :]:
320
- if anns is getattr (base_cls , "__annotations__" , None ):
321
- return {}
321
+ a = getattr (base_cls , attrib_name , None )
322
+ if attr is a :
323
+ return False
324
+
325
+ return True
326
+
327
+
328
+ def _get_annotations (cls ):
329
+ """
330
+ Get annotations for *cls*.
331
+ """
332
+ if _has_own_attribute (cls , "__annotations__" ):
333
+ return cls .__annotations__
322
334
323
- return anns
335
+ return {}
324
336
325
337
326
338
def _counter_getter (e ):
@@ -741,10 +753,10 @@ def _add_method_dunders(self, method):
741
753
)
742
754
743
755
744
- def _determine_eq_order (cmp , eq , order ):
756
+ def _determine_eq_order (cmp , eq , order , default_eq ):
745
757
"""
746
758
Validate the combination of *cmp*, *eq*, and *order*. Derive the effective
747
- values of eq and order.
759
+ values of eq and order. If *eq* is None, set it to *default_eq*.
748
760
"""
749
761
if cmp is not None and any ((eq is not None , order is not None )):
750
762
raise ValueError ("Don't mix `cmp` with `eq' and `order`." )
@@ -755,9 +767,10 @@ def _determine_eq_order(cmp, eq, order):
755
767
756
768
return cmp , cmp
757
769
758
- # If left None, equality is on and ordering mirrors equality.
770
+ # If left None, equality is set to the specified default and ordering
771
+ # mirrors equality.
759
772
if eq is None :
760
- eq = True
773
+ eq = default_eq
761
774
762
775
if order is None :
763
776
order = eq
@@ -768,14 +781,38 @@ def _determine_eq_order(cmp, eq, order):
768
781
return eq , order
769
782
770
783
784
+ def _determine_whether_to_implement (cls , flag , auto_detect , dunders ):
785
+ """
786
+ Check whether we should implement a set of methods for *cls*.
787
+
788
+ *flag* is the argument passed into @attr.s like 'init', *auto_detect* the
789
+ same as passed into @attr.s and *dunders* is a tuple of attribute names
790
+ whose presence signal that the user has implemented it themselves.
791
+
792
+ auto_detect must be False on Python 2.
793
+ """
794
+ if flag is True or flag is None and auto_detect is False :
795
+ return True
796
+
797
+ if flag is False :
798
+ return False
799
+
800
+ # Logically, flag is None and auto_detect is True here.
801
+ for dunder in dunders :
802
+ if _has_own_attribute (cls , dunder ):
803
+ return False
804
+
805
+ return True
806
+
807
+
771
808
def attrs (
772
809
maybe_cls = None ,
773
810
these = None ,
774
811
repr_ns = None ,
775
- repr = True ,
812
+ repr = None ,
776
813
cmp = None ,
777
814
hash = None ,
778
- init = True ,
815
+ init = None ,
779
816
slots = False ,
780
817
frozen = False ,
781
818
weakref_slot = True ,
@@ -786,6 +823,7 @@ def attrs(
786
823
auto_exc = False ,
787
824
eq = None ,
788
825
order = None ,
826
+ auto_detect = False ,
789
827
):
790
828
r"""
791
829
A class decorator that adds `dunder
@@ -810,6 +848,22 @@ def attrs(
810
848
:param str repr_ns: When using nested classes, there's no way in Python 2
811
849
to automatically detect that. Therefore it's possible to set the
812
850
namespace explicitly for a more meaningful ``repr`` output.
851
+ :param bool auto_detect: Instead of setting the *init*, *repr*, *eq*,
852
+ *order*, and *hash* arguments explicitly, assume they are set to
853
+ ``True`` **unless any** of the involved methods for one of the
854
+ arguments is implemented in the *current* class (i.e. it is *not*
855
+ inherited from some base class).
856
+
857
+ So for example by implementing ``__eq__`` on a class yourself,
858
+ ``attrs`` will deduce ``eq=False`` and won't create *neither*
859
+ ``__eq__`` *nor* ``__ne__``.
860
+
861
+ Passing ``True`` or ``False`` to *init*, *repr*, *eq*, *order*,
862
+ *cmp*, or *hash* overrides whatever *auto_detect* would determine.
863
+
864
+ *auto_detect* requires Python 3. Setting it ``True`` on Python 2 raises
865
+ a `PythonTooOldError`.
866
+
813
867
:param bool repr: Create a ``__repr__`` method with a human readable
814
868
representation of ``attrs`` attributes..
815
869
:param bool str: Create a ``__str__`` method that is identical to
@@ -878,8 +932,8 @@ def attrs(
878
932
879
933
:param bool weakref_slot: Make instances weak-referenceable. This has no
880
934
effect unless ``slots`` is also enabled.
881
- :param bool auto_attribs: If True, collect `PEP 526`_-annotated attributes
882
- (Python 3.6 and later only) from the class body.
935
+ :param bool auto_attribs: If `` True`` , collect `PEP 526`_-annotated
936
+ attributes (Python 3.6 and later only) from the class body.
883
937
884
938
In this case, you **must** annotate every field. If ``attrs``
885
939
encounters a field that is set to an `attr.ib` but lacks a type
@@ -944,8 +998,15 @@ def attrs(
944
998
.. versionadded:: 19.1.0 *auto_exc*
945
999
.. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01.
946
1000
.. versionadded:: 19.2.0 *eq* and *order*
1001
+ .. versionadded:: 20.1.0 *auto_detect*
947
1002
"""
948
- eq , order = _determine_eq_order (cmp , eq , order )
1003
+ if auto_detect and PY2 :
1004
+ raise PythonTooOldError (
1005
+ "auto_detect only works on Python 3 and later."
1006
+ )
1007
+
1008
+ eq_ , order_ = _determine_eq_order (cmp , eq , order , None )
1009
+ hash_ = hash # workaround the lack of nonlocal
949
1010
950
1011
def wrap (cls ):
951
1012
@@ -965,16 +1026,31 @@ def wrap(cls):
965
1026
cache_hash ,
966
1027
is_exc ,
967
1028
)
968
-
969
- if repr is True :
1029
+ if _determine_whether_to_implement (
1030
+ cls , repr , auto_detect , ("__repr__" ,)
1031
+ ):
970
1032
builder .add_repr (repr_ns )
971
1033
if str is True :
972
1034
builder .add_str ()
973
- if eq is True and not is_exc :
1035
+
1036
+ eq = _determine_whether_to_implement (
1037
+ cls , eq_ , auto_detect , ("__eq__" , "__ne__" )
1038
+ )
1039
+ if not is_exc and eq is True :
974
1040
builder .add_eq ()
975
- if order is True and not is_exc :
1041
+ if not is_exc and _determine_whether_to_implement (
1042
+ cls , order_ , auto_detect , ("__lt__" , "__le__" , "__gt__" , "__ge__" )
1043
+ ):
976
1044
builder .add_order ()
977
1045
1046
+ if (
1047
+ hash_ is None
1048
+ and auto_detect is True
1049
+ and _has_own_attribute (cls , "__hash__" )
1050
+ ):
1051
+ hash = False
1052
+ else :
1053
+ hash = hash_
978
1054
if hash is not True and hash is not False and hash is not None :
979
1055
# Can't use `hash in` because 1 == True for example.
980
1056
raise TypeError (
@@ -1002,7 +1078,9 @@ def wrap(cls):
1002
1078
)
1003
1079
builder .make_unhashable ()
1004
1080
1005
- if init is True :
1081
+ if _determine_whether_to_implement (
1082
+ cls , init , auto_detect , ("__init__" ,)
1083
+ ):
1006
1084
builder .add_init ()
1007
1085
else :
1008
1086
if cache_hash :
@@ -1805,7 +1883,7 @@ def __init__(
1805
1883
eq = None ,
1806
1884
order = None ,
1807
1885
):
1808
- eq , order = _determine_eq_order (cmp , eq , order )
1886
+ eq , order = _determine_eq_order (cmp , eq , order , True )
1809
1887
1810
1888
# Cache this descriptor here to speed things up later.
1811
1889
bound_setattr = _obj_setattr .__get__ (self , Attribute )
@@ -2148,7 +2226,10 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments):
2148
2226
attributes_arguments ["eq" ],
2149
2227
attributes_arguments ["order" ],
2150
2228
) = _determine_eq_order (
2151
- cmp , attributes_arguments .get ("eq" ), attributes_arguments .get ("order" )
2229
+ cmp ,
2230
+ attributes_arguments .get ("eq" ),
2231
+ attributes_arguments .get ("order" ),
2232
+ True ,
2152
2233
)
2153
2234
2154
2235
return _attrs (these = cls_dict , ** attributes_arguments )(type_ )
0 commit comments