Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conversion to set of custom collections class returns empty set in Python

I've recently written my own OrderedSet implementation since I had problems with the publicly available ordered/sorted set implementations. The class uses a dict proxy object in the background and mostly forwards operations. I implemented all (in my opinion) relevant methods, incl. __iter__, and calls like list(myset) work as expected.

However, the call set(myset) always returns empty sets.

Here's the full code of OrderedSet:

from typing import TypeVar, Generic, Optional, Iterable, Set, AbstractSet, Union, Iterator, Any, Dict

T = TypeVar("T")
S = TypeVar("S")


class OrderedSet(Generic[T], Set[T]):
    def __init__(self, base: Optional[Union[Dict[T, None], Iterable[T]]] = None):
        super().__init__()

        self.the_dict: Dict[T, None]
        if not base:
            self.the_dict = {}
        elif isinstance(base, dict):
            self.the_dict = base
        else:
            self.the_dict = dict.fromkeys(base)

    def __eq__(self, o: object) -> bool:
        return isinstance(o, OrderedSet) and list(self.the_dict) == list(o.the_dict)

    def __ne__(self, o: object) -> bool:
        return not self.__eq__(o)

    def __str__(self) -> str:
        return "{" + ", ".join(list(map(str, self.the_dict))) + "}"

    def __repr__(self) -> str:
        return f"OrderedSet({repr(self.the_dict)})"

    def add(self, element: T) -> None:
        self.the_dict = {**self.the_dict, **{element: None}}

    def clear(self) -> None:
        self.the_dict.clear()

    def copy(self) -> 'OrderedSet[T]':
        return OrderedSet(self.the_dict.copy())

    def difference(self, s: Iterable[Any]) -> 'OrderedSet[T]':
        return OrderedSet({e: None for e in self.the_dict if e not in s})

    def difference_update(self, s: Iterable[Any]) -> None:
        self.the_dict = {e: None for e in self.the_dict if e not in s}

    def discard(self, element: T) -> None:
        del self.the_dict[element]

    def intersection(self, s: Iterable[Any]) -> 'OrderedSet[T]':
        return OrderedSet({e: None for e in self.the_dict if e in s})

    def intersection_update(self, s: Iterable[Any]) -> None:
        self.the_dict = {e: None for e in self.the_dict if e in s}

    def isdisjoint(self, s: Iterable[Any]) -> bool:
        return self.the_dict.keys().isdisjoint(s)

    def issubset(self, s: Iterable[Any]) -> bool:
        return set(iter(self)).issubset(iter(s))

    def issuperset(self, s: Iterable[Any]) -> bool:
        return set(iter(self)).issuperset(iter(s))

    def pop(self) -> T:
        items = list(self.the_dict)
        result = items.pop()
        self.the_dict = dict.fromkeys(items)
        return result

    def remove(self, element: T) -> None:
        del self.the_dict[element]

    def symmetric_difference(self, s: Iterable[T]) -> 'OrderedSet[T]':
        return OrderedSet(
            dict.fromkeys([e for e in self.the_dict if e not in s] +
                          [e for e in s if e not in self.the_dict]))

    def symmetric_difference_update(self, s: Iterable[T]) -> None:
        self.the_dict = self.symmetric_difference(s).the_dict

    def union(self, s: Iterable[T]) -> 'OrderedSet[T]':
        return OrderedSet({**self.the_dict, **dict.fromkeys(s)})

    def update(self, s: Iterable[T]) -> None:
        self.the_dict = self.union(s).the_dict

    def __len__(self) -> int:
        return len(self.the_dict)

    def __contains__(self, o: object) -> bool:
        return o in self.the_dict

    def __iter__(self) -> Iterator[T]:
        return iter(self.the_dict)

    def __and__(self, s: AbstractSet[object]) -> 'OrderedSet[T]':
        return self.intersection(s)

    def __iand__(self, s: AbstractSet[object]) -> 'OrderedSet[T]':
        result = self.intersection(s)
        self.the_dict = result.the_dict
        return result

    def __or__(self, s: AbstractSet[S]) -> 'OrderedSet[Union[T, S]]':
        return self.union(s)

    def __ior__(self, s: AbstractSet[S]) -> 'OrderedSet[Union[T, S]]':
        result = self.union(s)
        self.the_dict = result.the_dict
        return result

    def __sub__(self, s: AbstractSet[Optional[T]]) -> 'OrderedSet[T]':
        return self.difference(s)

    def __isub__(self, s: AbstractSet[Optional[T]]) -> 'OrderedSet[T]':
        result = self.difference(s)
        self.the_dict = result.the_dict
        return result

    def __xor__(self, s: AbstractSet[S]) -> 'OrderedSet[Union[T, S]]':
        return self.symmetric_difference(s)

    def __ixor__(self, s: AbstractSet[S]) -> 'OrderedSet[Union[T, S]]':
        result = self.symmetric_difference(s)
        self.the_dict = result.the_dict
        return result

    def __le__(self, s: AbstractSet[object]) -> bool:
        return self.issubset(s)

    def __lt__(self, s: AbstractSet[object]) -> bool:
        return self.issubset(s) and len(self) < len(s)

    def __ge__(self, s: AbstractSet[object]) -> bool:
        return set(iter(self)) >= set(iter(s))

    def __gt__(self, s: AbstractSet[object]) -> bool:
        return set(iter(self)) > set(iter(s))

And here some example calls:

>>> from orderedset import OrderedSet
>>> s = OrderedSet([3, 1, 2])
>>> s
OrderedSet({3: None, 1: None, 2: None})
>>> list(s)
[3, 1, 2]
>>> set(s)
set()

Does anybody have an idea what's going on here? The set() function seems to be implemented in lengthy C code, which I don't understand well enough to deduce anything. It seems, though, that __iter__ is called for conversion to list, but not to set...

Any ideas?

Thanks a lot!

like image 564
dsteinhoefel Avatar asked May 04 '26 19:05

dsteinhoefel


1 Answers

Python 3.9 or above

Your class should be inheriting from collections.abc.Set instead of typing.Set(which has been deprecated anyway)

from collections.abc import Set
...
class OrderedSet(Generic[T], Set[T]):
    ...

obj = OrderedSet()
obj.add(1)
obj.add(2)
obj.add(3)
print(set(obj)) # {1,2,3}

Python 3.8 or lower

Before 3.9, using generics with collections.abc.Set was not possible. Inheriting from AbstractSet solves the problem.

from typing import Generic, AbstractSet
...
class OrderedSet(Generic[T], AbstractSet[T]):
    ...
like image 192
dumbPotato21 Avatar answered May 06 '26 09:05

dumbPotato21