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

假设我有一个可散列的 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 的构造函数期间启动并更改集合大小的风险?我希望这个问题非常罕见,以至于很难通过实验检测到,但可能会使我的程序崩溃一次。

我什至不觉得那个帖子上的各种评论者真的达成了共识
for obj in list(ws):
    ...

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

目前我已经编写了我的代码来破坏性地从 pop 中删除 WeakSet 项目,从而完全避免了迭代器。我不介意破坏性地这样做,因为在我的代码中我已经完成了 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 WeakValueDictionary 中使用的相同保护。

在旧的 Python 版本(3.0 或 2.6 及更早版本)上,此防护不存在。如果您需要支持 2.6 或更早版本,看起来将 keysvaluesitems 与弱 dict 类一起使用应该是安全的;我没有列出 WeakSet 的选项,因为当时 WeakSet 不存在。如果在 3.0 上有安全、无损的选项,我还没有找到,但希望没有人需要支持 3.0。

关于python - 将 python WeakSet 提供给列表构造函数是否安全?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/52693294/

10-09 15:54