custom dict that allows delete during iteration

后端 未结 7 1978
时光说笑
时光说笑 2020-12-08 09:53

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:

<
7条回答
  •  孤城傲影
    2020-12-08 10:28

    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

提交回复
热议问题