将 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 或更早版本,那么使用 keys
、values
和 items
以及弱字典 类 看起来应该是安全的;我没有列出 WeakSet 的选项,因为那时 WeakSet 还不存在。如果 3.0 有一个安全的、非破坏性的选项,我还没有找到,但希望没有人需要支持 3.0。
这个问题 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 或更早版本,那么使用 keys
、values
和 items
以及弱字典 类 看起来应该是安全的;我没有列出 WeakSet 的选项,因为那时 WeakSet 还不存在。如果 3.0 有一个安全的、非破坏性的选项,我还没有找到,但希望没有人需要支持 3.0。