Reputation: 5286
I have a couple int
child classes that represent different ranges. Some of their behavior is shared in an abstract class and I want them to be comparable among themselves and with plain integers but not with other different ranges. Hashing stays the same as integers, so there will be collisions, but I thought that wasn't an issue as an equality check will be performed to solve the hash collision.
from abc import ABC, abstractmethod
class Range(int, ABC):
@classmethod
@property
@abstractmethod
def min(self) -> int:
raise NotImplementedError
@classmethod
@property
@abstractmethod
def max(self) -> int:
raise NotImplementedError
def __init__(self, *args, **kwargs):
super().__init__()
# Check the lower and upper bounds
if not (self.min <= self <= self.max):
raise ValueError
def __eq__(self, other):
# If they are a range but not the same one just return False
if isinstance(other, Range) and not isinstance(other, self.__class__):
return False
return NotImplemented
def __ne__(self, other):
# If they are a range but not the same one just return True
if isinstance(other, Range) and not isinstance(other, self.__class__):
return True
return NotImplemented
def _check_other(self, other):
if isinstance(other, Range) and not isinstance(other, self.__class__):
raise NotImplementedError
def __lt__(self, other) -> bool:
self._check_other(other)
return int(self) < int(other)
def __le__(self, other) -> bool:
self._check_other(other)
return int(self) <= int(other)
def __gt__(self, other) -> bool:
self._check_other(other)
return int(self) > int(other)
def __ge__(self, other) -> bool:
self._check_other(other)
return int(self) >= int(other)
__hash__ = int.__hash__
class Range20(Range):
min = 1
max = 20
class Range100(Range):
min = 1
max = 100
m = {
Range20(1): -1,
Range20(2): -2,
Range100(1): 1,
Range100(2): 2,
}
assert Range20(1) == 1, "Range20(1) != 1"
assert Range20(1) != 2, "Range20(1) == 2"
assert Range20(1) == Range20(1), "Range20(1) != Range20(1)"
assert Range20(1) != Range20(2), "Range20(1) == Range20(2)"
assert Range20(1) != Range100(1), "Range20(1) == Range100(1)"
assert not (Range20(1) == Range100(1)), "Range20(1) == Range100(1) bis"
assert Range20(2) > 1, "Range20(2) <= 1"
assert Range20(2) >= 2, "Range20(2) < 2"
assert Range20(2) < 3, "Range20(2) >= 3"
assert Range20(2) <= 2, "Range20(2) > 2"
assert Range20(2) > Range20(1), "Range20(2) <= Range20(1)"
assert Range20(2) >= Range20(2), "Range20(2) < Range20(2)"
assert Range20(2) < Range20(3), "Range20(2) >= Range20(3)"
assert Range20(2) <= Range20(2), "Range20(2) > Range20(2)"
assert hash(Range20(1)) == hash(1), "hash(Range20(1)) != hash(1)"
assert hash(Range20(1)) == hash(Range100(1)), "hash(Range20(1)) != hash(Range100(1))"
assert Range20(1) in m, "Range20(1) not in m"
assert Range100(1) in m, "Range100(1) not in m"
assert 1 not in m, "1 in m"
All assertions should pass but some aren't passing right now. The last assertion may be hard to achieve as they are considered to be equal by __eq__
so not passing that one would be fine, but the previous two need to pass so that m[Range20(1)] == -1
and m[Range100(1)] == 1
(i.e., I can access the values in the map).
Edit: added comparisson methods and assertions but the result is still the same Edit 2: added additional assertions
Upvotes: 0
Views: 89
Reputation: 5286
@MoeNeuron guided me in the right direction, by adding some additional asserts I discovered that assert Range20(1) == Range20(1)
was failing. I thought that by returning NotImplemented
the int method would be able to handle those cases but it seems it doesn't. The solution is to use the following two modified methods:
def __eq__(self, other):
if isinstance(other, Range):
if not isinstance(other, self.__class__):
# If they are a range element but not from the same range
return False
# If they are a range element from the same range
return int(self) == int(other)
# If they are not a range element delegate to int
return NotImplemented
def __ne__(self, other):
if isinstance(other, Range):
if not isinstance(other, self.__class__):
# If they are a range element but not from the same range
return True
# If they are a range element from the same range
return int(self) != int(other)
# If they are not a range element delegate to int
return NotImplemented
With these modified methods, only the last assertion fails, which is acceptable as I said in the question.
Upvotes: 0
Reputation: 78
Changing this part here works with me (except assertion 17 fails -- which it doesn't actually align with i.e. the previous equality tests, unless you are wishing for something else I missed in your question):
def __eq__(self, other):
# If they are a range but not the same one just return False
if isinstance(other, Range) and not isinstance(other, self.__class__):
return False
elif isinstance(other, Range) and isinstance(other, self.__class__):
return True
return NotImplemented
def __ne__(self, other):
# If they are a range but not the same one just return True
if isinstance(other, Range) and not isinstance(other, self.__class__):
return True
elif isinstance(other, Range) and isinstance(other, self.__class__):
return False
return NotImplemented
Upvotes: 1