4 python recursion dictionary plist
我正在使用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)
Run Code Online (Sandbox Code Playgroud)
不幸的是,当我在测试.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
Run Code Online (Sandbox Code Playgroud)
因此,问题可能出在对自身的递归调用上,但是即使那样,它是否不应该只是从原始对象中删除呢?我不确定如何避免递归(或者如果这是正确的策略),但是由于它是.plist,所以我确实需要能够确定什么时候是字典或列表,并对其进行迭代以寻找(a)更多内容。字典来搜索,或者(b)我需要删除的导入的.plist中的实际键值对。
最终,这是部分非问题的,因为我将定期使用的文件具有已知的结构。但是,我真的希望创建一些无关紧要的对象,只要它是其中包含数组的Python字典即可。
遍历序列时在序列中添加项目或从序列中删除项目是最棘手的事情,并且对dicts来说是非法的(正如您刚刚发现的)。迭代时从字典中删除条目的正确方法是迭代键的快照。在Python 2.x中,dict.keys()提供了这样的快照。因此对于dicts解决方案是:
for key in mydict.keys():
if key == bad_value:
del mydict[key]
Run Code Online (Sandbox Code Playgroud)
正如cpizza在评论中提到的那样,对于python3,您需要使用以下命令显式创建快照list():
for key in list(mydict.keys()):
if key == bad_value:
del mydict[key]
Run Code Online (Sandbox Code Playgroud)
对于列表,尝试对索引的快照(即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']
Run Code Online (Sandbox Code Playgroud)
如您所见,这不是很成功。
此处已知的解决方案是对反向索引进行迭代,即:
>>> 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']
Run Code Online (Sandbox Code Playgroud)
这也适用于反向枚举,但是我们并不在乎。
总结一下:对于字典和列表,您需要两个不同的代码路径-并且还需要注意“非容器”值(既不是列表也不是字典的值),而在当前代码中则无需考虑。
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
Run Code Online (Sandbox Code Playgroud)
附带说明:切勿编写裸除条款。从来没有。确实,这应该是非法的语法。
这里是 @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
Run Code Online (Sandbox Code Playgroud)
还有一个带有正则表达式可调用的示例:
func = lambda key: re.match(r"^<div>", key)
clean_dict(obj, func)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3887 次 |
| 最近记录: |