将python WeakSet提供给列表构造函数是否安全?

Ed *_*hne 4 python weak-references

问题安全遍历WeakKeyDictionary和WeakValueDictionary没把我放心,因为我所希望的,而它的老不够,它的价值再次询问,而不是评论。

假设我有一个MyHashable可哈希的类,并且我想构建一个WeakSet

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

obj2.cycle_sibling = obj3
obj3.cycle_sibling = obj2

ws = WeakSet([obj1, obj2, obj3])
Run Code Online (Sandbox Code Playgroud)

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

del obj2
del obj3

list_remaining = list(ws)
Run Code Online (Sandbox Code Playgroud)

我举这个问题似乎声称这是蛮好的,但即使没有任何明确的for循环,我已不是已经冒着循环垃圾收集器的构造过程中踢list_remaining和更改集的大小?我希望这个问题非常罕见,以至于很难通过实验检测出来,但是一旦我的程序崩溃,它就会变成蓝色的月亮。

我什至不觉得那个帖子上的各种评论者是否真的同意了

for obj in list(ws):
    ...
Run Code Online (Sandbox Code Playgroud)

可以,但是他们似乎都以为list(ws)自己可以一直运行而不会崩溃,我什至不敢相信。list构造函数是否避免以某种方式使用迭代器,从而不关心集合大小的变化?是否可以在list构造函数期间不进行垃圾回收,因为它list是内置的?

就目前而言,我写我的代码,以破坏性pop的物品出来的WeakSet,从而完全避免迭代器。我不介意破坏性地这样做,因为在我的代码中,WeakSet无论如何我已经完成了。但是我不知道自己是否偏执。

use*_*ica 5

文档令人沮丧地缺乏有关此方面的信息,但是从实现的角度来看,我们可以WeakSet.__iter__防范这种问题。

在上的迭代期间WeakSet,weakref回调会将引用添加到待处理的删除列表中,而不是直接从基础集中删除引用。如果元素在迭代到达之前就死了,则迭代器不会产生该元素,但是您不会得到segfault或a 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()
Run Code Online (Sandbox Code Playgroud)

这是__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
Run Code Online (Sandbox Code Playgroud)

这是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)
Run Code Online (Sandbox Code Playgroud)

您还可以看到WeakKeyDictionary和中使用的相同防护WeakValueDictionary


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

  • 很好的答案,但请注意,防护实现实际上不是线程安全的(与代码中的注释相反)。 (2认同)