在list、set、dict迭代访问时删除元素,会出现什么问题?分别简单验证下,
# list
>>> container = [1, 2, 3]
>>> for x in container:
... container.remove(x)
... print x
...
1
3
# set
>>> container = {1, 2, 3}
>>> for x in container:
... container.remove(x)
... print x
...
1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: Set changed size during iteration
# dict
>>> container = {1: 1, 2: 2, 3: 3}
>>> for x in container:
... del container[x]
... print x
...
1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration
set、dict在进行上述操作的时候会抛出异常,而list却是默默把这个问题掩盖了。
直接解决的方法很简单,在可能进行删除的时候创建一份新的容器进行遍历,
# set
>>> container = {1, 2, 3}
>>> for x in set(container):
... container.remove(x)
... print x
...
1
2
3
创建新容器虽然容易,但一些时候也依然容易出错。很多代码在初次实现的时候可能并不存在删除这一操作,因此是不会这样实现,当后面再进行修改时,可能就会遇上先前埋下的问题。
更多的时候,用容器元素并不是简单类型,是否删除也是一系列判定逻辑之后的结果。当然,如果在遍历的地方都进行最初的处理,问题能够得到解决。但实际运行中,真正需要进行删除是少数情况下才会遇到。当整个函数高频运行的时候,避免这种无意义的容器创建是不是会更合适,特别是当性能遇到问题时,能优化一点也是一点。比如下面这段代码,
import weakref
class Foobar(object):
def __init__(self, container):
self.container = weakref.proxy(container)
def update(self):
# ...
self.destroy()
def destroy(self):
self.container.remove(self)
class Container(object):
def __init__(self):
self.foobar_list = [Foobar(self)]
def update(self):
for foobar in list(self.foobar_list):
foobar.update()
只有当update中触发了某些条件时才需要删除。完整的调用链很长,因此为了保证不出现最初的问题,每次update中都创建了一个新列表用以处理删除。对于这种情况,目前想到的策略是维护两个列表,分别维护待删除元素与全部容器。在遍历的开始,先移除之前计算出来的待删除元素。
import weakref
class Foobar(object):
def __init__(self, container):
self.container = weakref.proxy(container)
def update(self):
self.destroy()
def destroy(self):
self.container.suspend(self)
class Container(object):
def __init__(self):
self.foobar_list = [Foobar(self)]
self.suspend_list = []
def suspend(self, foobar):
self.suspend_list.append(foobar)
def clear_suspends(self):
if not self.suspend_list:
return
for foobar in self.suspend_list:
self.foobar_list.remove(foobar)
self.suspend_list = []
def update(self):
self.clear_suspends()
for foobar in self.foobar_list:
foobar.update()
修改之后的代码反而多了一点,但带来的收益就是避免了重复创建容器。此外,也将删除处理逻辑流程化。不过这种方式对整个运算模式是有一定要求的。不符合的,并不适合这么处理。