Python中将自定义集合类转换为集合后返回空集。

3

我最近编写了自己的OrderedSet实现,因为我使用公共可用的有序/排序集合实现时遇到问题。该类在后台使用字典代理对象并大多转发操作。我实现了所有(在我看来)相关的方法,包括__iter__,并且像list(myset)这样的调用按预期工作。

然而,调用set(myset)总是返回空集合。

以下是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))

以下是一些示例调用:

>>> 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()

有人知道这里发生了什么吗?set()函数似乎是用冗长的C代码实现的,我不太理解足以推断出任何东西。看起来,__iter__被调用以将其转换为list,但未转换为set...

有任何想法吗?

非常感谢!


1
有趣... >_< 我尝试过调整 __getattribute__ 等内容,以查看调用了哪个内部函数,但没有进展。 - Adam.Er8
2个回答

1

Python 3.9 或以上版本

你的类应该继承自collections.abc.Set而非typing.Set(已经被废弃了)

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 或更低版本

在3.9之前,使用泛型与collections.abc.Set是不可能的。继承AbstractSet类可以解决这个问题。

from typing import Generic, AbstractSet
...
class OrderedSet(Generic[T], AbstractSet[T]):
    ...

当应用此更改时,我收到错误TypeError:'ABCMeta'对象不可订阅。你试过你的修复吗?它对你有用吗? - dsteinhoefel
看起来当你使用collections.abc时,你必须在Set[T]中省略[T] - dsteinhoefel
你的Python版本是多少?如果你使用的是旧版本的Python,请尝试从AbstractSet继承。这里是一个在Python 3.7上运行的代码版本:https://ideone.com/bgJVIR。 - dumbPotato21
选择了这个答案,因为调用 super().__init__(self.the_dict.keys()) 会产生不同的问题,特别是我必须将所有其他调用转发到 discard 等。 - dsteinhoefel
1
编辑了答案以包含此内容。这个应该一开始就有的。对于很多人来说,“3.9”仍然是相当新的。 - dumbPotato21
显示剩余2条评论

0
你需要初始化super,否则它将是一个空集合。
    def __init__(self, base: Optional[Union[Dict[T, None], Iterable[T]]] = None):
        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)
        super().__init__(self.the_dict.keys())

谢谢,问题已解决!非常有趣。从collections.abc.Set继承(其他答案)对我没有产生影响;相反,我收到了一个TypeError。 - dsteinhoefel
实际上,另一种方法也可以奏效,那就是不要继承Set!否则,我还必须将所有改变状态的请求(例如discard())转发到超类(否则set()无法工作),这会带来额外的开销。当我从Set中删除继承时,调用set()时会调用iter()。 - dsteinhoefel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接