将 python WeakSet 赋予列表构造函数是否安全?

Is it safe to give a python WeakSet to a list constructor?

这个问题 Safely iterating over WeakKeyDictionary and WeakValueDictionary 并没有像我希望的那样让我放心,而且它已经足够老了,值得再问而不是评论。

假设我有一个可散列的 class MyHashable,我想构建一个 WeakSet:

obj1 = MyHashable()
obj2 = MyHashable()
obj3 = MyHashable()

obj2.cycle_sibling = obj3
obj3.cycle_sibling = obj2

ws = WeakSet([obj1, obj2, obj3])

然后我删除了一些局部变量,并转换为一个列表,为后面的循环做准备:

del obj2
del obj3

list_remaining = list(ws)

我引用的问题似乎声称这很好,但即使没有任何类型的显式 for 循环,我是否 已经 冒着循环垃圾收集器的风险在 list_remaining 的构造函数中启动并更改集合的大小?我希望这个问题非常罕见,很难通过实验检测到,但可能会在极少数情况下使我的程序崩溃。

我什至不认为对此 post 的各种评论是否真的达成了一致意见

for obj in list(ws):
    ...

没问题,但他们似乎都假设 list(ws) 本身可以 运行 一直通过而不会崩溃,我什至不相信这一点。 list 构造函数是否以某种方式避免使用迭代器,因此不关心集合大小的变化?因为 list 是内置的,所以在 list 构造函数期间不能进行垃圾回收吗?

目前,我已经将我的代码编写为 WeakSet 中破坏性的 pop 项,从而完全避免了迭代器。我不介意破坏性地这样做,因为那时在我的代码中我已经完成了 WeakSet 。但是不知道是不是我偏执了

令人沮丧的是,文档缺乏这方面的信息,但查看 implementation,我们可以看到 WeakSet.__iter__ 可以防止此类问题。

WeakSet 的迭代过程中,weakref 回调将添加对待处理删除列表的引用,而不是直接从基础集合中删除引用。如果一个元素在迭代到达它之前死亡,迭代器将不会产生该元素,但你不会得到段错误或 RuntimeError: Set changed size during iteration 或任何东西。

这是守卫(不是线程安全的,不管评论怎么说):

class _IterationGuard:
    # This context manager registers itself in the current iterators of the
    # weak container, such as to delay all removals until the context manager
    # exits.
    # This technique should be relatively thread-safe (since sets are).

    def __init__(self, weakcontainer):
        # Don't create cycles
        self.weakcontainer = ref(weakcontainer)

    def __enter__(self):
        w = self.weakcontainer()
        if w is not None:
            w._iterating.add(self)
        return self

    def __exit__(self, e, t, b):
        w = self.weakcontainer()
        if w is not None:
            s = w._iterating
            s.remove(self)
            if not s:
                w._commit_removals()

这里是 __iter__ 使用守卫的地方:

class WeakSet:
    ...
    def __iter__(self):
        with _IterationGuard(self):
            for itemref in self.data:
                item = itemref()
                if item is not None:
                    # Caveat: the iterator will keep a strong reference to
                    # `item` until it is resumed or closed.
                    yield item

这里是 weakref 回调检查守卫的地方:

def _remove(item, selfref=ref(self)):
    self = selfref()
    if self is not None:
        if self._iterating:
            self._pending_removals.append(item)
        else:
            self.data.discard(item)

您还可以看到 WeakKeyDictionary and WeakValueDictionary 中使用的相同守卫。


在旧 Python 版本(3.0 或 2.6 及更早版本)上,此守卫不存在。如果您需要支持 2.6 或更早版本,那么使用 keysvaluesitems 以及弱字典 类 看起来应该是安全的;我没有列出 WeakSet 的选项,因为那时 WeakSet 还不存在。如果 3.0 有一个安全的、非破坏性的选项,我还没有找到,但希望没有人需要支持 3.0。