python 从列表中删除词典

dxpyg8gm  于 2022-12-02  发布在  Python
关注(0)|答案(8)|浏览(164)

如果我有一个字典列表,可以说:

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

并且我想删除id为2的字典(或者名称为'john'),那么通过编程实现这一点的最有效方法是什么(也就是说,我不知道列表中条目的索引,所以不能简单地弹出它)?

mec1mxoz

mec1mxoz1#

thelist[:] = [d for d in thelist if d.get('id') != 2]

Edit:由于在一条评论中对这段代码的性能表示了一些怀疑(一些是基于对Python性能特征的误解,一些是基于超出给定规范的假设,即在列表中正好有一个dict的键'id'的值为2),我希望在这一点上提供保证。

在一个旧的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微秒用于随机洗牌(需要确保要移除的元素不总是在相同的点;- )和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

(use当然,如果你使用的是Python 2.6或更高版本,则使用next内置方法,而不是.next方法)--但是如果满足移除条件的dict的数量不是正好一个,则这段代码会崩溃。

$ 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

在这里,洗牌可以被移除,因为我们知道,已经有三个等间距的指令需要移除了。

$ 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

完全并驾齐驱,99个元素中甚至只有3个元素要被删除。随着列表更长,重复次数更多,这当然就更成立了:

$ 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微秒;- ).避免或批评简单、直接和性能完全足够的解决方案(比如针对“从列表中删除一些项”问题的列表解析)是Knuth和Hoare著名的“过早优化是编程中所有罪恶的根源”的一个特别恶劣的例子!-)

jdgnovmf

jdgnovmf2#

下面是一个使用列表解析的方法(假设你将列表命名为'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无论如何都会返回一个迭代器,所以列表解析确实是最好的选择,正如Alex所建议的。

w1jd8yoj

w1jd8yoj3#

# assume ls contains your list
for i in range(len(ls)):
    if ls[i]['id'] == 2:
        del ls[i]
        break

平均来说,它可能比列表解析方法更快,因为如果它在早期找到了有问题的项,它不会遍历整个列表。

bz4sfanl

bz4sfanl4#

这不是一个正确的答案(因为我认为你已经有一些相当好的),但是...你有没有考虑过有一个字典的<id>:<name>,而不是一个字典列表?

a0zr77ik

a0zr77ik5#

假设你的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]

您也可以对此进行修改,以确保无论python版本如何,获取id key都不会引发keyerror
如果dicts[i].get('id ',无)== 2

jvlzgdj9

jvlzgdj96#

您可以尝试以下操作:

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循环。

mbyulnm0

mbyulnm07#

您可以尝试以下方式:

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 模块来更快地找到你正在寻找的对象。

ma8fv8wu

ma8fv8wu8#

pep448的更新中可以看出,当用一个临时变量(比如row)迭代一个字典列表时,你可以使用row来获取当前迭代的字典,合并新的键,或者使用布尔运算来过滤字典列表中的字典。
记住
行将输出一个新的字典。
例如,您的字典起始清单:

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

如果我们想要过滤掉ID2:

data = [{**row} for row in data if data['id']!=2]

如果要过滤掉John:

data = [{**row} for row in data if data['name']!='John']

与问题没有直接关系,但如果您想添加新密钥:

data = [{**row, 'id_name':str(row['id'])+'_'+row['name']} for row in data]

它也比公认的解决方案快了一点点。

相关问题