我正在使用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, inscrub(loadedplist) File "formscrub.py", line 19, in scrub for key, value in iterator: RuntimeError: dictionary changed size during iteration
因此,问题可能出在对自身的递归调用上,但是即使那样,它是否不应该只是从原始对象中删除呢?我不确定如何避免递归(或者如果这是正确的策略),但是由于它是.plist,所以我确实需要能够确定什么时候是字典或列表,并对其进行迭代以寻找(a)更多内容。字典来搜索,或者(b)我需要删除的导入的.plist中的实际键值对。
最终,这是部分非问题的,因为我将定期使用的文件具有已知的结构。但是,我真的希望创建一些无关紧要的对象,只要它是其中包含数组的Python字典即可。
遍历序列时在序列中添加项目或从序列中删除项目是最棘手的事情,并且对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
附带说明:切勿编写裸除条款。从来没有。确实,这应该是非法的语法。