递归删除字典键?

 壳牌盛行 发布于 2023-02-09 09:28

我正在使用Python 2.7与plistlib以嵌套dict / array格式导入.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 
    scrub(loadedplist)
  File "formscrub.py", line 19, in scrub
    for key, value in iterator:
RuntimeError: dictionary changed size during iteration

因此,问题可能出在对自身的递归调用上,但是即使那样,它是否不应该只是从原始对象中删除呢?我不确定如何避免递归(或者如果这是正确的策略),但是由于它是.plist,所以我确实需要能够确定什么时候是字典或列表,并对其进行迭代以寻找(a)更多内容。字典来搜索,或者(b)我需要删除的导入的.plist中的实际键值对。

最终,这是部分非问题的,因为我将定期使用的文件具有已知的结构。但是,我真的希望创建一些无关紧要的对象,只要它是其中包含数组的Python字典即可。

1 个回答
  • 遍历序列时在序列中添加项目或从序列中删除项目是最棘手的事情,并且对dicts来说是非法的(正如您刚刚发现的)。迭代时从字典中删除条目的正确方法是迭代键的快照。在Python 2.x中,dict.keys()提供了这样的快照。因此对于dicts解决方案是:

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

    正如cpizza在评论中提到的那样,对于python3,您需要使用以下命令显式创建快照list()

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

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

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

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

    总结一下:对于字典和列表,您需要两个不同的代码路径-并且还需要注意“非容器”值(既不是列表也不是字典的值),而在当前代码中则无需考虑。

    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
    

    附带说明:切勿编写裸除条款。从来没有。确实,这应该是非法的语法。

    2023-02-09 09:32 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有