【问题标题】:Conversion to set of custom collections class returns empty set in Python转换为自定义集合类集在 Python 中返回空集
【发布时间】:2022-01-23 01:07:09
【问题描述】:

我最近编写了自己的 OrderedSet 实现,因为我遇到了公开可用的有序/排序集实现的问题。该类在后台使用 dict 代理对象,主要是转发操作。我实现了所有(在我看来)相关的方法,包括。 __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...

有什么想法吗?

非常感谢!

【问题讨论】:

  • 引人入胜... >___getattribute__ 和其他东西以查看调用了哪个内部函数,但一无所获

标签: python collections set


【解决方案1】:

Python3.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' object is not subscriptable。你试过你的修复吗?它对你有用吗?
  • ...在使用collections.abc 时,您必须在Set[T] 中省略[T]
  • 你的 python 版本是什么?如果您使用的是旧版本的 python,请尝试从 AbstractSet 继承。 Here's 在 python 3.7 上运行的代码版本
  • 但是,您会注意到,文档特别指出“自 3.9 版起已弃用:collections.abc.Set 现在支持 []。请参阅 PEP 585 和通用别名类型。”对于AbstractSet
  • 编辑了答案以包含此内容。可能一开始就应该在那里。 3.9 对很多人来说还是很新鲜的。
【解决方案2】:

你需要初始化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。
  • 实际上,同样有效的只是从 Set 继承!否则,我还必须将所有更改状态的请求(例如discard())也转发到超类(或set() 不起作用),这会带来额外的开销。当我从 Set 中删除继承时,调用 set() 时会调用 iter()。
猜你喜欢
  • 2015-07-15
  • 1970-01-01
  • 2012-09-27
  • 1970-01-01
  • 1970-01-01
  • 2013-12-16
  • 2010-09-25
  • 2015-10-10
  • 1970-01-01
相关资源
最近更新 更多