在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()

修改之后的代码反而多了一点,但带来的收益就是避免了重复创建容器。此外,也将删除处理逻辑流程化。不过这种方式对整个运算模式是有一定要求的。不符合的,并不适合这么处理。