簡體   English   中英

python - 遞歸刪除字典鍵?

[英]python - recursively deleting dict keys?

我正在使用帶有plistlib Python 2.7 以嵌套的 dict/array 形式導入 .plist,然后查找特定鍵並在我看到的任何地方將其刪除。

當涉及到我們在辦公室使用的實際文件時,我已經知道在哪里可以找到這些值——但我寫腳本的想法是我沒有,希望我不必如果文件結構發生變化,或者我們需要對其他類似文件做同樣的事情,請在將來進行更改。

不幸的是,我似乎在迭代時試圖修改 dict,但我不確定這實際上是如何發生的,因為我使用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,我確實需要能夠確定事物何時是 dicts 或列表,並遍歷它們以搜索(a)更多dicts 來搜索,或 (b) 導入的 .plist 中我需要刪除的實際鍵值對。

最終,這是一個部分非問題,因為我將定期使用的文件具有已知結構。 然而,我真的希望創建一些不關心它正在使用的對象的嵌套或順序的東西,只要它是一個包含數組的 Python dict。

在迭代此序列時向/從序列中添加或刪除項目充其量是棘手的,並且使用 dicts 只是非法的(正如您剛剛發現的那樣)。 在迭代時從 dict 中刪除條目的正確方法是迭代鍵的快照。 在 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

作為旁注:永遠不要寫一個空的except子句。 從來沒有 這應該是非法語法,真的。

這是@bruno desthuilliers 之一的通用版本,帶有callable的鍵來測試。

def clean_dict(obj, func):
    """
    This method scrolls the entire 'obj' to delete every key for which the 'callable' returns
    True

    :param obj: a dictionary or a list of dictionaries to clean
    :param func: a callable that takes a key in argument and return True for each key to delete
    """
    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 func(key):
                del obj[key]
            else:
                clean_dict(obj[key], func)
    elif isinstance(obj, list):
        for i in reversed(range(len(obj))):
            if func(obj[i]):
                del obj[i]
            else:
                clean_dict(obj[i], func)

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

以及一個帶有正則表達式可調用的示例:

func = lambda key: re.match(r"^<div>", key)

clean_dict(obj, func)
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])

輸出

In [30]: d = {1:{2:3}, 2:{3:4}, 5:{6:{2:3}, 7:{1:2, 2:3}}, 3:4}

In [31]: delKeys(d, 2)

In [32]: d
Out[32]: {1: {}, 3: 4, 5: {6: {}, 7: {1: 2}}}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM