UPDATED based on Lennart Regebro\'s answer
Suppose you iterate through a dictionary, and sometimes need to delete an element. The following is very efficient:
<
Naive implementation for Python 2.x and 3.x:
import sys
from collections import deque
def _protect_from_delete(func):
def wrapper(self, *args, **kwargs):
try:
self._iterating += 1
for item in func(self, *args, **kwargs):
yield item
finally:
self._iterating -= 1
self._delete_pending()
return wrapper
class DeletableDict(dict):
def __init__(self, *args, **kwargs):
super(DeletableDict, self).__init__(*args, **kwargs)
self._keys_to_delete = deque()
self._iterating = 0
if sys.version_info[0] != 3:
iterkeys = _protect_from_delete(dict.iterkeys)
itervalues = _protect_from_delete(dict.itervalues)
iteritems = _protect_from_delete(dict.iteritems)
else:
keys = _protect_from_delete(dict.keys)
values = _protect_from_delete(dict.values)
items = _protect_from_delete(dict.items)
__iter__ = _protect_from_delete(dict.__iter__)
def __delitem__(self, key):
if not self._iterating:
return super(DeletableDict, self).__delitem__(key)
self._keys_to_delete.append(key)
def _delete_pending(self):
for key in self._keys_to_delete:
super(DeletableDict, self).__delitem__(key)
self._keys_to_delete.clear()
if __name__ == '__main__':
dct = DeletableDict((i, i*2) for i in range(15))
if sys.version_info[0] != 3:
for k, v in dct.iteritems():
if k < 5:
del dct[k]
print(dct)
for k in dct.iterkeys():
if k > 8:
del dct[k]
print(dct)
for k in dct:
if k < 8:
del dct[k]
print(dct)
else:
for k, v in dct.items():
if k < 5:
del dct[k]
print(dct)
When iterating over keys, items or values it sets flag self._iterating. In __delitem__ it checks for ability to delete item, and stores keys in temporary queue. At the end of iterations it deletes all pending keys.
It's very naive implementation, and I wouldn't recommend to use it in production code.
EDIT
Added support for Python 3 and improvements from @jsbueno comments.
Python 3 run on Ideone.com