Python: удалить словарь из списка - PullRequest
48 голосов
/ 06 августа 2009

Если у меня есть список словарей, скажите:

[{'id': 1, 'name': 'paul'},
{'id': 2, 'name': 'john'}]

и я хотел бы удалить словарь с id из 2 (или именем john), что является наиболее эффективным способом программного решения (то есть я не знаю индекс записи в списке, чтобы его нельзя было просто вставить).

Ответы [ 7 ]

92 голосов
/ 06 августа 2009
thelist[:] = [d for d in thelist if d.get('id') != 2]

Редактировать : поскольку некоторые комментарии были высказаны в комментарии по поводу производительности этого кода (некоторые основаны на неправильном понимании характеристик производительности Python, другие на предположении, помимо указанных спецификаций, что в список со значением 2 для ключа 'id'), я хотел бы предложить заверение на этот счет.

На старой Linux-системе, измеряя этот код:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(99)]; import random" "thelist=list(lod); random.shuffle(thelist); thelist[:] = [d for d in thelist if d.get('id') != 2]"
10000 loops, best of 3: 82.3 usec per loop

из которых около 57 микросекунд для random.shuffle (необходимо, чтобы гарантировать, что удаляемый элемент не ВСЕГДА в одном месте ;-) и 0,65 микросекунд для начальной копии (кто бы ни беспокоился о влиянии на производительность мелких копий Python) списки наиболее очевидно на обед ;-), необходимо, чтобы избежать изменения исходного списка в цикле (таким образом, у каждого участка цикла есть, что удалить; -).

Когда известно, что нужно удалить только один элемент, его можно найти и удалить еще быстрее:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(99)]; import random" "thelist=list(lod); random.shuffle(thelist); where=(i for i,d in enumerate(thelist) if d.get('id')==2).next(); del thelist[where]"
10000 loops, best of 3: 72.8 usec per loop

(конечно, используйте встроенный next вместо .next, если вы используете Python 2.6 или выше), но этот код не работает, если число диктовок, которые удовлетворяют условию удаления, не совсем точно один. Обобщая это, мы имеем:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*3; import random" "thelist=list(lod); where=[i for i,d in enumerate(thelist) if d.get('id')==2]; where.reverse()" "for i in where: del thelist[i]"
10000 loops, best of 3: 23.7 usec per loop

где перетасовка может быть удалена, потому что, как мы знаем, уже есть три равноудаленных диктовки для удаления. И listcomp, без изменений, хорошо обходится:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*3; import random" "thelist=list(lod); thelist[:] = [d for d in thelist if d.get('id') != 2]"
10000 loops, best of 3: 23.8 usec per loop

полностью шея и шея, при этом нужно удалить только 3 элемента из 99. С более длинными списками и большим количеством повторений, это, конечно, еще больше:

$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*133; import random" "thelist=list(lod); where=[i for i,d in enumerate(thelist) if d.get('id')==2]; where.reverse()" "for i in where: del thelist[i]"
1000 loops, best of 3: 1.11 msec per loop
$ python -mtimeit -s"lod=[{'id':i, 'name':'nam%s'%i} for i in range(33)]*133; import random" "thelist=list(lod); thelist[:] = [d for d in thelist if d.get('id') != 2]"
1000 loops, best of 3: 998 usec per loop

В общем, очевидно, что не стоит использовать тонкость создания и обращения списка индексов для удаления, в отличие от совершенно простого и очевидного понимания списка, чтобы, возможно, получить 100 наносекунд в одном небольшом случае - и потерять 113 микросекунд в больше ;-). Избежание или критика простых, простых и совершенно адекватных производительности решений (таких как составление списков для этого общего класса проблем «удалить некоторые элементы из списка») является особенно неприятным примером известного тезиса Кнута и Хоара о том, что «преждевременная оптимизация - это корень зла в программировании "! -)

8 голосов
/ 06 августа 2009

Вот способ сделать это с пониманием списка (при условии, что вы называете свой список 'foo'):

[x for x in foo if not (2 == x.get('id'))]

Заменить 'john' == x.get('name') или что-либо в зависимости от ситуации.

filter также работает:

foo.filter(lambda x: x.get('id')!=2, foo)

А если вам нужен генератор, вы можете использовать itertools:

itertools.ifilter(lambda x: x.get('id')!=2, foo)

Однако, начиная с Python 3, filter все равно будет возвращать итератор, так что понимание списка действительно лучший выбор, как предположил Алекс.

7 голосов
/ 06 августа 2009

Это не совсем правильный ответ (так как я думаю, что у вас уже есть несколько хороших из них), но ... вы рассматривали наличие словаря <id>:<name> вместо списка словарей?

3 голосов
/ 06 августа 2009
# assume ls contains your list
for i in range(len(ls)):
    if ls[i]['id'] == 2:
        del ls[i]
        break

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

1 голос
/ 06 августа 2009

Вы можете попробовать следующее:

a = [{'id': 1, 'name': 'paul'},
     {'id': 2, 'name': 'john'}]

for e in range(len(a) - 1, -1, -1):
    if a[e]['id'] == 2:
        a.pop(e)

Если Вы не можете всплыть с самого начала - всплыть с конца, это не разрушит цикл for.

0 голосов
/ 09 января 2019

Предполагается, что ваша версия Python версии 3.6 или выше, и что вам не нужен удаленный элемент, это будет дешевле ...

Если словари в списке уникальны:

for i in range(len(dicts)):
    if dicts[i].get('id') == 2:
        del dicts[i]
        break

Если вы хотите удалить все подходящие элементы:

for i in range(len(dicts)):
    if dicts[i].get('id') == 2:
        del dicts[i]

Вы также можете быть уверены, что получение ключа id не вызовет keyerror независимо от версии python

if dicts [i] .get ('id', None) == 2

0 голосов
/ 06 августа 2009

Вы можете попробовать что-то вроде следующего:

def destructively_remove_if(predicate, list):
      for k in xrange(len(list)):
          if predicate(list[k]):
              del list[k]
              break
      return list

  list = [
      { 'id': 1, 'name': 'John' },
      { 'id': 2, 'name': 'Karl' },
      { 'id': 3, 'name': 'Desdemona' } 
  ]

  print "Before:", list
  destructively_remove_if(lambda p: p["id"] == 2, list)
  print "After:", list

Если вы не создадите что-то похожее на индекс по вашим данным, я не думай, что ты можешь добиться большего успеха, чем делать грубую силу » сканировать "по всему списку. Если ваши данные отсортированы по ключу вы вы можете использовать модуль bisect для найти объект, который вы ищете несколько быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...