Skip to content

Commit

Permalink
Stop comparing/ordering subclasses
Browse files Browse the repository at this point in the history
This has been deprecated for a year and was raising a warning.
  • Loading branch information
hynek committed Sep 7, 2019
1 parent 0b91364 commit a25b24a
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 52 deletions.
3 changes: 3 additions & 0 deletions changelog.d/570.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` do not consider consider subclasses comparable anymore.

This has been deprecated since 18.2.0 and was raising a ``DeprecationWarning`` for over a year.
50 changes: 15 additions & 35 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import sys
import threading
import uuid
import warnings

from operator import itemgetter

Expand Down Expand Up @@ -865,6 +864,9 @@ def attrs(
:class:`DeprecationWarning` if the classes compared are subclasses of
each other. ``__eq`` and ``__ne__`` never tried to compared subclasses
to each other.
.. versionchanged:: 19.2.0
``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider
consider subclasses comparable anymore.
.. versionadded:: 18.2.0 *kw_only*
.. versionadded:: 18.2.0 *cache_hash*
.. versionadded:: 19.1.0 *auto_exc*
Expand Down Expand Up @@ -1092,12 +1094,6 @@ def __ne__(self, other):
return not result


WARNING_CMP_ISINSTANCE = (
"Comparision of subclasses using __%s__ is deprecated and will be removed "
"in 2019."
)


def _make_cmp(cls, attrs):
attrs = [a for a in attrs if a.cmp]

Expand Down Expand Up @@ -1147,53 +1143,37 @@ def __lt__(self, other):
"""
Automatically created by attrs.
"""
if isinstance(other, self.__class__):
if other.__class__ is not self.__class__:
warnings.warn(
WARNING_CMP_ISINSTANCE % ("lt",), DeprecationWarning
)
if other.__class__ is self.__class__:
return attrs_to_tuple(self) < attrs_to_tuple(other)
else:
return NotImplemented

return NotImplemented

def __le__(self, other):
"""
Automatically created by attrs.
"""
if isinstance(other, self.__class__):
if other.__class__ is not self.__class__:
warnings.warn(
WARNING_CMP_ISINSTANCE % ("le",), DeprecationWarning
)
if other.__class__ is self.__class__:
return attrs_to_tuple(self) <= attrs_to_tuple(other)
else:
return NotImplemented

return NotImplemented

def __gt__(self, other):
"""
Automatically created by attrs.
"""
if isinstance(other, self.__class__):
if other.__class__ is not self.__class__:
warnings.warn(
WARNING_CMP_ISINSTANCE % ("gt",), DeprecationWarning
)
if other.__class__ is self.__class__:
return attrs_to_tuple(self) > attrs_to_tuple(other)
else:
return NotImplemented

return NotImplemented

def __ge__(self, other):
"""
Automatically created by attrs.
"""
if isinstance(other, self.__class__):
if other.__class__ is not self.__class__:
warnings.warn(
WARNING_CMP_ISINSTANCE % ("ge",), DeprecationWarning
)
if other.__class__ is self.__class__:
return attrs_to_tuple(self) >= attrs_to_tuple(other)
else:
return NotImplemented

return NotImplemented

return eq, ne, __lt__, __le__, __gt__, __ge__

Expand Down
45 changes: 28 additions & 17 deletions tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -1457,13 +1457,11 @@ class TestMakeCmp:
Tests for _make_cmp().
"""

@pytest.mark.parametrize(
"op", ["__%s__" % (op,) for op in ("lt", "le", "gt", "ge")]
)
def test_subclasses_deprecated(self, recwarn, op):
def test_subclasses_cannot_be_compared(self):
"""
Calling comparison methods on subclasses raises a deprecation warning;
calling them on identical classes does not..
Calling comparison methods on subclasses raises a TypeError.
We use the actual operation so we get an error raised on Python 3.
"""

@attr.s
Expand All @@ -1474,18 +1472,31 @@ class A(object):
class B(A):
pass

getattr(A(42), op)(A(42))
getattr(B(42), op)(B(42))
a = A(42)
b = B(42)

assert [] == recwarn.list
assert a <= a
assert a >= a
assert not a < a
assert not a > a

getattr(A(42), op)(B(42))
assert (
NotImplemented
== a.__lt__(b)
== a.__le__(b)
== a.__gt__(b)
== a.__ge__(b)
)

w = recwarn.pop()
if not PY2:
with pytest.raises(TypeError):
a <= b

assert [] == recwarn.list
assert isinstance(w.message, DeprecationWarning)
assert (
"Comparision of subclasses using %s is deprecated and will be "
"removed in 2019." % (op,)
) == w.message.args[0]
with pytest.raises(TypeError):
a >= b

with pytest.raises(TypeError):
a < b

with pytest.raises(TypeError):
a > b

0 comments on commit a25b24a

Please sign in to comment.