2013-12-19 2 views
1

Я использую Python 2.7 с plistlib, чтобы импортировать .plist в вложенной форме dict/array, затем найдите конкретный ключ и удалите его, где бы я его ни увидел.python - рекурсивно удаляет ключи dict?

Когда дело доходит до фактических файлов, с которыми мы работаем в офисе, я уже знаю, где найти значения, но я написал свой сценарий с идеей, что я этого не сделал, в надежде, что я не буду «Вносить изменения в будущем, если структура файла изменится, или мы должны сделать то же самое с другими подобными файлами.

К сожалению, я пытаюсь изменить dict во время итерации по нему, но я не уверен, как это происходит на самом деле, поскольку я использую iteritems() и enumerate(), чтобы получить генераторы и работать с ними вместо объекта I Я действительно работаю.

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, мне нужно определить, когда вещи являются диктофонами или списками, и перебирать их в поисках (a) более dicts для поиска или (b) фактическая пара ключ-значение в импортированном .plist, которую мне нужно удалить.

В конечном счете, это частично без проблем, поскольку файлы, с которыми я буду работать регулярно, имеют известную структуру. Тем не менее, я действительно надеялся создать что-то, что не заботится о гнездовании или порядке объекта, с которым он работает, до тех пор, пока он является питоном Python с массивами в нем.

+2

Что именно вопрос? – jdotjdot

+0

Ah geez:/i даже не попал в соответствующую загадку –

ответ

2

Добавление или удаление элементов в/из последовательности, в то время как итерация по этой последовательности сложна в лучшем случае и просто незаконна (как вы только что открыли) с помощью dicts. Правильный способ удаления записей из dict при повторении по нему - это итерация моментального снимка клавиш. В Python 2.x, dict.keys() предоставляет такой снимок. Таким образом, для dicts решение:

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

Для списков, пытаясь итерацию на снимке индексов (т.е. for i in len(thelist):) приведет к IndexError как только что-то удаляется (очевидно, по крайней мере, последнего индекса будет больше нет), и даже если вы не можете пропустить один или несколько элементов (поскольку удаление элемента делает синхронизацию последовательности индексов с самим списком). enumate безопасен против 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="_this_is_bad"): 
    if isinstance(obj, dict): 
     for k in obj.keys(): 
      if k == bad: 
       del obj[k] 
      else: 
       scrub(obj[k], bad) 
    elif isinstance(obj, list): 
     for i in reversed(range(len(obj))): 
      if obj[i] == bad: 
       del obj[i] 
      else: 
       scrub(obj[i], bad) 

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

В качестве примечания: никогда не писать голый, за исключением п. Никогда никогда. На самом деле это должен быть незаконный синтаксис.

+0

Возможно, мое понимание того, что такое итератор, является неполным. Я понял, что он существует независимо от объекта, который он представлял. Возможно, это неправильно. Тем не менее, если я неправильно выполнил ваше предложение, это приведет к 'TypeError: объект типа 'int' не имеет len()'. Устранение этой ошибки (явно) приводит только к ошибке максимальной глубины рекурсии. –

+1

@Stick: Ваше понимание итераторов действительно неполное. Вы найдете официальный документ здесь: http://docs.python.org/2/library/stdtypes.html#iterator-types. wrt/TypeError, код, который вы опубликовали, имеет ту же проблему - попробуйте 'перечислять (42)'. –

+1

@Stick: пересмотрен ответ. –

0
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}}} 
+0

это было бы довольно звездно, если бы .plists были просто вложенными dicts, но, к сожалению, те, с которыми я работаю, также создают массивы. Но мне это очень нравится, и я думаю, что это движение в правильном направлении. –

+0

Если бы вы могли опубликовать пример структуры данных, с которой имеете дело, я мог бы попытаться обновить свой ответ – inspectorG4dget

Смежные вопросы