diff --git a/README.md b/README.md index a0c1494..8164a13 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Please also see the [documentation](https://matthiasdiener.github.io/orderedsets - https://pypi.org/project/stableset/ (no frozen/immutable class) - https://pypi.org/project/orderedset/ (Cython, no frozen/immutable class) - https://pypi.org/project/Ordered-set-37/ (no frozen/immutable class) +- https://github.com/grantjenks/python-sortedcollections (no frozen/immutable class) ### Discussions diff --git a/orderedsets/__init__.py b/orderedsets/__init__.py index bc0bc70..5b7be72 100644 --- a/orderedsets/__init__.py +++ b/orderedsets/__init__.py @@ -35,8 +35,16 @@ __version__ = importlib_metadata.version(__package__ or __name__) -from collections.abc import Iterable, Iterator, Set -from typing import AbstractSet, Any, Hashable, TypeVar +import sys +from collections.abc import Iterable, Iterator +from typing import Any, Hashable, TypeVar + +if sys.version_info < (3, 9): # pragma: no cover + from typing import AbstractSet as MutableSet + from typing import AbstractSet as Set +else: + from collections.abc import MutableSet, Set + T = TypeVar("T", bound=Hashable) T_cov = TypeVar("T_cov", covariant=True, bound=Hashable) @@ -46,7 +54,7 @@ class _NotProvided: pass -class OrderedSet(AbstractSet[T]): +class OrderedSet(MutableSet[T]): """A set class that preserves insertion order. It implements the same API as :class:`set` and can be used as a drop-in @@ -63,12 +71,6 @@ def __init__(self, items: Iterable[T] | type[_NotProvided] = _NotProvided)\ # mypy thinks 'items' can still be Type[_NotProvided] here. self._dict = dict.fromkeys(items) # type: ignore[arg-type] - def __eq__(self, other: object) -> bool: - """Return whether this set is equal to *other*.""" - return (isinstance(other, Set) - and len(self) == len(other) - and all(i in other for i in self)) - def __repr__(self) -> str: """Return a string representation of this set.""" cls_name = self.__class__.__name__ @@ -130,10 +132,6 @@ def intersection_update(self, *others: Iterable[T]) -> None: self._dict = dict.fromkeys(common_keys) - def isdisjoint(self, s: Iterable[T]) -> bool: - """Return whether this set is disjoint with *s*.""" - return self._dict.keys().isdisjoint(s) - def issubset(self, s: Iterable[T]) -> bool: """Return whether this set is a subset of *s*.""" return all(i in s for i in self) @@ -221,42 +219,18 @@ def __ixor__(self, s: Set[Any]) -> OrderedSet[T]: self._dict = result._dict return result - def __le__(self, s: Set[T]) -> bool: - """Return whether this set is a subset of *s*.""" - return self.issubset(s) - - def __lt__(self, s: Set[T]) -> bool: - """Return whether this set is a proper subset of *s*.""" - return len(self) < len(s) and self.issubset(s) - - def __ge__(self, s: Set[T]) -> bool: - """Return whether this set is a superset of *s*.""" - return set(self) >= set(s) - - def __gt__(self, s: Set[T]) -> bool: - """Return whether this set is a proper superset of *s*.""" - return len(self) > len(s) and set(self) > set(s) - -class FrozenOrderedSet(AbstractSet[T_cov]): +class FrozenOrderedSet(Set[T_cov]): """A frozen set class that preserves insertion order. It implements the same API as :class:`frozenset` and can be used as a drop-in replacement for that class when ordering is desired. """ - def __init__(self, items: Iterable[T_cov] | type[_NotProvided] = _NotProvided)\ - -> None: - """Create a new :class:`FrozenOrderedSet`, optionally initialized \ - with *items*.""" - if items is _NotProvided: - self._dict = {} - else: - # type-ignore-reason: - # mypy thinks 'items' can still be Type[_NotProvided] here. - self._dict = dict.fromkeys(items) # type: ignore[arg-type] + _dict: dict[T_cov, None] + _my_hash: int | None = None - self._my_hash: int | None = None + __init__ = OrderedSet.__init__ def __reduce__(self) -> tuple[Any, ...]: """Return pickling information for this set.""" @@ -280,88 +254,25 @@ def __hash__(self) -> int: self._my_hash = hash(frozenset(self)) return self._my_hash - def __eq__(self, other: object) -> bool: - """Return whether this set is equal to *other*.""" - return (isinstance(other, Set) - and len(self) == len(other) - and all(i in other for i in self)) - - def __repr__(self) -> str: - """Return a string representation of this set.""" - cls_name = self.__class__.__name__ - if len(self) == 0: - return f"{cls_name}()" - return f"{cls_name}({{" + ", ".join([repr(k) for k in self._dict]) + "})" - - def __len__(self) -> int: - """Return the number of elements in this set.""" - return len(self._dict) - - def __contains__(self, o: object) -> bool: - """Return whether *o* is in this set.""" - return o in self._dict - - def __iter__(self) -> Iterator[T_cov]: - """Return an iterator over the elements of this set.""" - return iter(self._dict) - - def copy(self) -> FrozenOrderedSet[T_cov]: - """Return a shallow copy of this set.""" - return self.__class__(self._dict) - - def difference(self, *others: Iterable[T_cov]) -> FrozenOrderedSet[T_cov]: - """Return the difference of this set and *others*.""" - if not others: - return self.__class__(self._dict) - other_elems = set.union(*map(set, others)) - items = [item for item in self._dict if item not in other_elems] - return self.__class__(items) - - def intersection(self, *others: Iterable[T_cov]) -> FrozenOrderedSet[T_cov]: - """Return the intersection of this set and *others*.""" - if not others: - return self.__class__(self._dict) - - oth = set(self).intersection(*others) - result_elements = [element for element in self._dict if element in oth] - - return self.__class__(result_elements) - - def symmetric_difference(self, s: Iterable[T_cov]) -> FrozenOrderedSet[T_cov]: - """Return the symmetric difference of this set and *s*.""" - return self.__class__( - dict.fromkeys([e for e in self._dict if e not in s] - + [e for e in s if e not in self._dict])) - - def isdisjoint(self, s: Iterable[T_cov]) -> bool: - """Return whether this set is disjoint with *s*.""" - return self._dict.keys().isdisjoint(s) # pylint: disable=no-member - - def issubset(self, s: Iterable[T_cov]) -> bool: - """Return whether this set is a subset of *s*.""" - return all(i in s for i in self) - - def issuperset(self, s: Iterable[T_cov]) -> bool: - """Return whether this set is a superset of *s*.""" - return set(self).issuperset(set(s)) + __repr__ = OrderedSet.__repr__ + __len__ = OrderedSet.__len__ + __contains__ = OrderedSet.__contains__ + __iter__ = OrderedSet.__iter__ - def union(self, *others: Iterable[T_cov]) -> FrozenOrderedSet[T_cov]: - """Return the union of this set and *others*.""" - return self.__class__(list(self._dict) - + [e for other in others for e in other]) + if sys.version_info >= (3, 9): + __eq__ = Set.__eq__ # needed for mypy - def __and__(self, s: Set[T_cov]) -> FrozenOrderedSet[T_cov]: - """Return the intersection of this set and *s*.""" - return self.intersection(s) + copy = OrderedSet.copy - def __or__(self, s: Set[Any]) -> FrozenOrderedSet[T_cov]: - """Return the union of this set and *s*.""" - return self.union(s) + difference = OrderedSet.difference + intersection = OrderedSet.intersection + symmetric_difference = OrderedSet.symmetric_difference - def __sub__(self, s: Set[T_cov]) -> FrozenOrderedSet[T_cov]: - """Return the difference of this set and *s*.""" - return self.difference(s) + issubset = OrderedSet.issubset + issuperset = OrderedSet.issuperset + union = OrderedSet.union - def __xor__(self, s: Set[Any]) -> FrozenOrderedSet[T_cov]: - """Return the symmetric difference of this set and *s*.""" - return self.symmetric_difference(s) + __and__ = OrderedSet.__and__ + __or__ = OrderedSet.__or__ + __sub__ = OrderedSet.__sub__ + __xor__ = OrderedSet.__xor__ diff --git a/test/test_orderedsets.py b/test/test_orderedsets.py index 1977158..80fb22d 100644 --- a/test/test_orderedsets.py +++ b/test/test_orderedsets.py @@ -797,16 +797,10 @@ def test_isinstance(cls: T_ordered_set[int]) -> None: if cls in mutable_set_types: assert isinstance(cls(), OrderedSet) assert not isinstance(cls(), FrozenOrderedSet) - # assert isinstance(cls(), abc_MutableSet) # see xfail test below + import sys + if sys.version_info >= (3, 9): + assert isinstance(cls(), abc_MutableSet) else: assert not isinstance(cls(), OrderedSet) assert isinstance(cls(), FrozenOrderedSet) assert not isinstance(cls(), abc_MutableSet) - - -@pytest.mark.xfail(reason="OrderedSet is not a MutableSet") -def test_isinstance_xfail() -> None: - from collections.abc import MutableSet as abc_MutableSet - - cls = OrderedSet - assert isinstance(cls(), abc_MutableSet)