python-sortedcontainers
python-sortedcontainers copied to clipboard
Return NotImplemented for SortedSet.__and__
Binary operators such as __eq__, __and__, etc. should return NotImplemented in the event that the given argument is an unsupported type rather than raising an error. This allows the other object to patch in its own implementation.
Roughly speaking, it means code should be written like this:
from typing import Iterable
class SortedSet:
def __and__(self, other):
if isinstance(other, Iterable): # Or `hasattr(type(other), "__iter__")`.
return self.intersection(other)
else:
return NotImplemented # `self & other` then checks `other.__and__(self)`.
This allows cases such as this:
class Interval: # Not a set e.g. does not contain `__iter__`.
def __and__(self, other):
if isinstance(other, Interval):
...
elif isinstance(other, Iterable):
return {x for x in other if x in self}
else:
return NotImplemented
def __contains__(self, other):
...
sorted_set & interval # Expected set, got TypeError.
I'm familiar with return NotImplemented and agree it would be helpful. Here's the code:
def intersection(self, *iterables):
"""Return the intersection of two or more sets as a new sorted set.
The `intersection` method also corresponds to operator ``&``.
``ss.__and__(iterable)`` <==> ``ss & iterable``
The intersection is all values that are in this sorted set and each of
the other `iterables`.
>>> ss = SortedSet([1, 2, 3, 4, 5])
>>> ss.intersection([4, 5, 6, 7])
SortedSet([4, 5])
:param iterables: iterable arguments
:return: new sorted set
"""
intersect = self._set.intersection(*iterables)
return self._fromset(intersect, key=self._key)
__and__ = intersection
__rand__ = __and__
SortedSet.__and__ calls self._set.intersection(*iterables) and that's what raises the TypeError.
It may be easy enough to fix with:
def __and__(self, other):
"""Return the intersection of two sets as a new sorted set.
``ss.__and__(iterable)`` <==> ``ss & iterable``
"""
intersect = self._set & other
return self._fromset(intersect, key=self._key)
But it's disappointing the __and__ = intersection trick doesn't work.
I think maybe it ought to be:
def __and__(self, other):
"""Return the intersection of two sets as a new sorted set.
``ss.__and__(iterable)`` <==> ``ss & iterable``
"""
intersect = self._set.__and__(other)
if intersect is NotImplemented:
return NotImplemented
return self._fromset(intersect, key=self._key)
Actually I don't believe set & iterable works, only set & set works, so it'd have to be either set.intersection(iterable) with a try block or an if check beforehand.