python递归删除dict键?

2024-05-15 01:25:59 发布

您现在位置:Python中文网/ 问答频道 /正文

我使用Python2.7和plistlib以嵌套dict/array的形式导入一个.plist,然后查找某个特定的键并在我看到它的任何地方删除它。在

当涉及到我们在办公室中使用的实际文件时,我已经知道在哪里可以找到这些值——但是我在编写脚本时,我的想法是我没有,希望将来如果文件结构发生变化,或者我们需要对其他类似的文件做同样的更改,我就不必进行更改。在

不幸的是,我似乎试图在迭代dict时修改它,但我不确定这是如何发生的,因为我使用iteritems()和{}来获取生成器并使用这些生成器,而不是实际使用的对象。在

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)

不幸的是,当我在test.plist文件上运行此程序时,我得到以下错误:

^{pr2}$

所以问题可能是对自身的递归调用,但即便如此,它不应该只是从原始对象中删除吗?我不确定如何避免递归(或者如果这是正确的策略),但是由于它是一个.plist,我确实需要能够确定什么时候是dict或list,并在它们上进行迭代,以搜索(a)要搜索的更多dict,或者(b)需要删除的imported.plist中的实际键值对。在

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


Tags: 文件the对象keycount结构dictfile
2条回答

在迭代这个序列时向序列中添加或从序列中删除项是非常棘手的,并且对于dict来说是非法的(正如您刚刚发现的)。迭代dict时从dict中删除条目的正确方法是迭代键的快照。在python2.x中,dict.keys()提供了这样一个快照。因此,对于dicts来说,解决方案是:

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

正如cpizza在评论中提到的,对于python3,您需要使用list()显式地创建快照:

^{pr2}$

对于列表,尝试迭代索引的快照(即for i in len(thelist):)会在删除任何内容后立即导致索引错误(显然,因为至少最后一个索引将不再存在),即使不存在,也可能跳过一个或多个项(因为删除项会使索引序列与列表本身不同步)。enumerate对IndexError是安全的(因为当列表中不再有“next”项时,迭代将自行停止,但您仍将跳过项:

>>> 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']

不太成功,正如你所看到的。在

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

>>> 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']

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

所以总结一下:您需要dict和list的两个不同的代码路径,并且您还需要处理“not container”值(既不是列表也不是dict的值),这是当前代码中不需要考虑的内容。在

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

作为补充说明:从不编写一个空白的except子句。从不从未。这应该是非法语法,真的。在

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])

输出

^{pr2}$

相关问题 更多 >

    热门问题