从列表中移除字典
假设我有一个字典的列表,比如:
[{'id': 1, 'name': 'paul'},
{'id': 2, 'name': 'john'}]
如果我想要删除一个id
为2的字典(或者名字是'john'
的字典),那么用程序来实现这个操作,最有效的方法是什么呢?(也就是说,我不知道这个字典在列表中的位置,所以不能直接用弹出的方法来删除它。)
10 个回答
# assume ls contains your list
for i in range(len(ls)):
if ls[i]['id'] == 2:
del ls[i]
break
这个方法可能比列表推导式的方法平均来说更快,因为如果它很早就找到了想要的项目,就不需要遍历整个列表了。
这里有一种使用列表推导的方法(假设你的列表叫做'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 所建议的。
thelist[:] = [d for d in thelist if d.get('id') != 2]
编辑: 有人对这段代码的性能表示了一些疑问(有些是因为误解了Python的性能特点,有些是因为假设在给定的条件下,列表中恰好有一个字典的'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
(当然,如果你使用的是Python 2.6或更高版本,使用内置的next
函数,而不是.next
方法)——但如果满足删除条件的字典数量不止一个,这段代码就不适用了。一般来说,我们可以这样理解:
$ 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所说的“过早优化是编程中一切邪恶的根源”!-)