python - 递归删除字典键?

问题描述 投票:4回答:2

我正在使用带有plistlib的Python 2.7以嵌套的dict /数组形式导入.plist,然后查找特定的键并在我看到的任何地方删除它。

当谈到我们在办公室工作的实际文件时,我已经知道在哪里可以找到这些值 - 但是我写了我的脚本,其中包含我没有的想法,希望我不需要如果文件结构发生更改,或者我们需要对其他类似文件执行相同操作,请在将来进行更改。

不幸的是,我似乎试图修改一个字典,同时迭代它,但我不确定它是如何实际发生的,因为我正在使用iteritems()enumerate()来获取生成器并与那些工作而不是我正在工作的对象用。

def scrub(someobject, badvalue='_default'): ##_default isn't the real variable
    """Walks the structure of a plistlib-created dict and finds all the badvalues and viciously eliminates them.

Can optionally be passed a different key to search for."""
    count = 0

    try:
        iterator = someobject.iteritems()
    except AttributeError:
        iterator = enumerate(someobject)

    for key, value in iterator:
        try:
            scrub(value)
        except:
            pass
        if key == badvalue:
            del someobject[key]
            count += 1

    return "Removed {count} instances of {badvalue} from {file}.".format(count=count, badvalue=badvalue, file=file)

不幸的是,当我在我的测试.plist文件上运行它时,我收到以下错误:

Traceback (most recent call last):
  File "formscrub.py", line 45, in <module>
    scrub(loadedplist)
  File "formscrub.py", line 19, in scrub
    for key, value in iterator:
RuntimeError: dictionary changed size during iteration

所以问题可能是对自身的递归调用,但即使这样它也不应该只是从原始对象中删除?我不确定如何避免递归(或者如果这是正确的策略)但是因为它是一个.plist,我确实需要能够确定什么时候是词或列表并迭代它们以寻找(a)更多dicts to search,或(b)导入的.plist中我需要删除的实际键值对。

最终,这是一个部分无问题,因为我将定期处理的文件具有已知的结构。但是,我真的希望创建一些不关心它正在使用的对象的嵌套或顺序的东西,只要它是一个带有数组的Python dict。

python recursion dictionary plist
2个回答
7
投票

在迭代这个序列时向序列中添加项目或从序列中删除项目充其量是棘手的,并且只是非法(如您刚才发现的)使用dicts。在迭代它时从dict中删除条目的正确方法是迭代键的快照。在Python 2.x中,dict.keys()提供了这样的快照。所以对于dicts,解决方案是:

for key in mydict.keys():
    if key == bad_value:
        del mydict[key]

正如cpizza在评论中提到的,对于python 3,你需要使用qazxsw poi显式创建快照:

list()

对于列表,尝试迭代索引的快照(即for key in list(mydict.keys()): if key == bad_value: del mydict[key] )会在删除任何内容后立即导致IndexError(显然因为至少最后一个索引将不再存在),即使不是,您也可以跳过一个或者更多项目(因为删除项目会使索引序列与列表本身不同步)。 for i in len(thelist):对IndexError是安全的(因为当列表中没有'next'项时,迭代将自行停止,但你仍然会跳过项:

enumerate

你可以看到,并不是很成功。

这里已知的解决方案是迭代反向索引,即:

>>> mylist = list("aabbccddeeffgghhii")
>>> for x, v  in enumerate(mylist):
...     if v in "bdfh":
...         del mylist[x]
>>> print mylist
['a', 'a', 'b', 'c', 'c', 'd', 'e', 'e', 'f', 'g', 'g', 'h', 'i', 'i']

这也适用于反向枚举,但我们并不关心。

总而言之:你需要两个不同的代码和列表代码路径 - 你还需要处理“not container”值(既不是列表也不是dicts的值),这是你当前代码中没有注意的事情。

>>> mylist = list("aabbccddeeffgghhii")
>>> for x in reversed(range(len(mylist))):
...     if mylist[x] in "bdfh":
...         del mylist[x]
>>> print mylist
['a', 'a', 'c', 'c', 'e', 'e', 'g', 'g', 'i', 'i']

作为旁注:永远不要写一个裸的except子句。永远不能。这应该是非法的语法,真的。


0
投票
def scrub(obj, bad_key="_this_is_bad"):
    if isinstance(obj, dict):
        # the call to `list` is useless for py2 but makes
        # the code py2/py3 compatible
        for key in list(obj.keys()):
            if key == bad_key:
                del obj[key]
            else:
                scrub(obj[key], bad_key)
    elif isinstance(obj, list):
        for i in reversed(range(len(obj))):
            if obj[i] == bad_key:
                del obj[i]
            else:
                scrub(obj[i], bad_key)

    else:
        # neither a dict nor a list, do nothing
        pass

产量

def walk(d, badvalue, answer=None, sofar=None):
    if sofar is None:
        sofar = []
    if answer is None:
        answer = []
    for k,v in d.iteritems():
        if k == badvalue:
            answer.append(sofar + [k])
        if isinstance(v, dict):
            walk(v, badvalue, answer, sofar+[k])
    return answer

def delKeys(d, badvalue):
    for path in walk(d, badvalue):
        dd = d
        while len(path) > 1:
            dd = dd[path[0]]
            path.pop(0)
        dd.pop(path[0])
© www.soinside.com 2019 - 2024. All rights reserved.