过滤字典以仅包含某些键?

我有一个dict,它有一大堆条目。我只对其中的几个感兴趣。有没有一种简单的方法来修剪所有其他的?

555150 次浏览

下面是python 2.6中的一个例子:

>>> a = {1:1, 2:2, 3:3}
>>> dict((key,value) for key, value in a.iteritems() if key == 1)
{1: 1}

过滤部分是if语句。

如果您只想选择非常多的键中的几个,则此方法比delnan的答案慢。

构建一个新的字典:

dict_you_want = { your_key: old_dict[your_key] for your_key in your_keys }

使用字典理解。

如果您使用的版本缺少它们(即Python 2.6及更早版本),请将其设为dict((your_key, old_dict[your_key]) for ...)。它是相同的,尽管更丑。

请注意,这与jnnnnn的版本不同,对于任何大小的#EYZ都具有稳定的性能(仅取决于your_keys的数量)。在速度和内存方面。由于这是一个生成器表达式,它一次处理一个项目,并且它不会查看所有old_dict项目。

删除所有就地:

unwanted = set(keys) - set(your_dict)
for unwanted_key in unwanted: del your_dict[unwanted_key]

给定您的原始字典orig和您感兴趣的keys条目集:

filtered = dict(zip(keys, [orig[k] for k in keys]))

这不如delnan的答案好,但应该适用于所有感兴趣的Python版本。然而,它对原始字典中存在的keys的每个元素都很脆弱。

基于德尔南接受的答案。

如果你想要的密钥之一不在old_dict中怎么办?delnan解决方案将抛出一个你可以捕获的KeyError异常。如果这不是你需要的,也许你想:

  1. 仅包括在old_dict和wanted_keys集中执行的键。

    old_dict = {'name':"Foobar", 'baz':42}
    wanted_keys = ['name', 'age']
    new_dict = {k: old_dict[k] for k in set(wanted_keys) & set(old_dict.keys())}
    
    
    >>> new_dict
    {'name': 'Foobar'}
    
  2. have a default value for keys that's not set in old_dict.

    default = None
    new_dict = {k: old_dict[k] if k in old_dict else default for k in wanted_keys}
    
    
    >>> new_dict
    {'age': None, 'name': 'Foobar'}
    

这个函数会起作用:

def include_keys(dictionary, keys):
"""Filters a dict by only including certain keys."""
key_set = set(keys) & set(dictionary.keys())
return {key: dictionary[key] for key in key_set}

就像delnan的版本一样,这个版本使用字典理解,并且对于大型字典具有稳定的性能(仅取决于您允许的键的数量,而不是字典中的键的总数)。

就像MyGGan的版本一样,这个版本允许您的键列表包含字典中可能不存在的键。

作为奖励,这是相反的,您可以通过排除原始中的某些键来创建字典:

def exclude_keys(dictionary, keys):
"""Filters a dict by excluding certain keys."""
key_set = set(dictionary.keys()) - set(keys)
return {key: dictionary[key] for key in key_set}

请注意,与delnan的版本不同,该操作不是就地完成的,因此性能与字典中的键数有关。然而,这样做的好处是该函数不会修改提供的字典。

编辑:添加了一个单独的函数,用于从字典中排除某些键。

这一个班轮lambda应该工作:

dictfilt = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ])

这里有一个例子:

my_dict = {"a":1,"b":2,"c":3,"d":4}
wanted_keys = ("c","d")


# run it
In [10]: dictfilt(my_dict, wanted_keys)
Out[10]: {'c': 3, 'd': 4}

这是一个基本的列表理解,迭代你的字典键(i in x),如果键存在于你想要的键列表(y)中,则输出一个元组(key, value)对列表。

稍微更优雅的字典理解:

foodict = {k: v for k, v in mydict.items() if k.startswith('foo')}

您可以使用我的功能库中的项目函数执行此操作:

from funcy import project
small_dict = project(big_dict, keys)

也看看select_keys

代码1:

dict = { key: key * 10 for key in range(0, 100) }
d1 = {}
for key, value in dict.items():
if key % 2 == 0:
d1[key] = value

代码2:

dict = { key: key * 10 for key in range(0, 100) }
d2 = {key: value for key, value in dict.items() if key % 2 == 0}

代码3:

dict = { key: key * 10 for key in range(0, 100) }
d3 = { key: dict[key] for key in dict.keys() if key % 2 == 0}

所有代码的性能都是通过timeit来测量的,使用的数字=1000,并为每段代码收集1000次。

在此处输入图片描述

对于python 3.6,三种过滤方式的性能几乎相同。对于python 2.7,代码3略快。

简称:

[s.pop(k) for k in list(s.keys()) if k not in keep]

正如大多数答案所建议的,为了保持简洁,我们必须创建一个重复的对象,无论是list还是dict。这个创建了一个一次性的list,但删除了原始dict中的键。

另一种选择:

content = dict(k1='foo', k2='nope', k3='bar')
selection = ['k1', 'k3']
filtered = filter(lambda i: i[0] in selection, content.items())

但是你得到的是filter()返回的list(Python 2)或迭代器(Python 3),而不是dict

如果我们想创建一个删除选定键的新字典,我们可以使用字典理解
例如:

d = {
'a' : 1,
'b' : 2,
'c' : 3
}
x = {key:d[key] for key in d.keys() - {'c', 'e'}} # Python 3
y = {key:d[key] for key in set(d.keys()) - {'c', 'e'}} # Python 2.*
# x is {'a': 1, 'b': 2}
# y is {'a': 1, 'b': 2}

这是另一个在一个衬里中使用del的简单方法:

for key in e_keys: del your_dict[key]

e_keys是要排除的键列表。它将更新您的判词,而不是为您提供新的判词。

如果您想要一个新的输出字典,请在删除之前复制字典:

new_dict = your_dict.copy()           #Making copy of dict


for key in e_keys: del new_dict[key]

你可以使用python-benedict,它是一个字典子类。

安装:pip install python-benedict

from benedict import benedict


dict_you_want = benedict(your_dict).subset(keys=['firstname', 'lastname', 'email'])

它在GitHub上是开源的:https://github.com/fabiocaccamo/python-benedict


声明:我是这个图书馆的作者。

我们可以像这样简单地使用lambda函数:

>>> dict_filter = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ])
>>> large_dict = {"a":1,"b":2,"c":3,"d":4}
>>> new_dict_keys = ("c","d")
>>> small_dict=dict_filter(large_dict, new_dict_keys)
>>> print(small_dict)
{'c': 3, 'd': 4}
>>>

这似乎是最简单的方法:

d1 = {'a':1, 'b':2, 'c':3}
d2 = {k:v for k,v in d1.items() if k in ['a','c']}

我也喜欢这样做来解压缩值:

a, c = {k:v for k,v in d1.items() if k in ['a','c']}.values()


这是我的方法,支持嵌套字段,如mongo查询。

如何使用:

>>> obj = { "a":1, "b":{"c":2,"d":3}}
>>> only(obj,["a","b.c"])
{'a': 1, 'b': {'c': 2}}

only函数:

def only(object,keys):
obj = {}
for path in keys:
paths = path.split(".")
rec=''
origin = object
target = obj
for key in paths:
rec += key
if key in target:
target = target[key]
origin = origin[key]
rec += '.'
continue
if key in origin:
if rec == path:
target[key] = origin[key]
else:
target[key] = {}
target = target[key]
origin = origin[key]
rec += '.'
else:
target[key] = None
break
return obj

我们也可以通过稍微更优雅的字典理解来实现这一点:

my_dict = {"a":1,"b":2,"c":3,"d":4}


filtdict = {k: v for k, v in my_dict.items() if k.startswith('a')}
print(filtdict)

根据问题的标题,人们会期望在适当的地方过滤字典-几个答案建议了这样做的方法-仍然不清楚什么是一个明显的方式-我添加了一些时间:

import random
import timeit
import collections


repeat = 3
numbers = 10000


setup = ''
def timer(statement, msg='', _setup=None):
print(msg, min(
timeit.Timer(statement, setup=_setup or setup).repeat(
repeat, numbers)))


timer('pass', 'Empty statement')


dsize = 1000
d = dict.fromkeys(range(dsize))
keep_keys = set(random.sample(range(dsize), 500))
drop_keys = set(random.sample(range(dsize), 500))


def _time_filter_dict():
"""filter a dict"""
global setup
setup = r"""from __main__ import dsize, collections, drop_keys, \
keep_keys, random"""
timer('d = dict.fromkeys(range(dsize));'
'collections.deque((d.pop(k) for k in drop_keys), maxlen=0)',
"pop inplace - exhaust iterator")
timer('d = dict.fromkeys(range(dsize));'
'drop_keys = [k for k in d if k not in keep_keys];'
'collections.deque('
'(d.pop(k) for k in list(d) if k not in keep_keys), maxlen=0)',
"pop inplace - exhaust iterator (drop_keys)")
timer('d = dict.fromkeys(range(dsize));'
'list(d.pop(k) for k in drop_keys)',
"pop inplace - create list")
timer('d = dict.fromkeys(range(dsize));'
'drop_keys = [k for k in d if k not in keep_keys];'
'list(d.pop(k) for k in drop_keys)',
"pop inplace - create list (drop_keys)")
timer('d = dict.fromkeys(range(dsize))\n'
'for k in drop_keys: del d[k]', "del inplace")
timer('d = dict.fromkeys(range(dsize));'
'drop_keys = [k for k in d if k not in keep_keys]\n'
'for k in drop_keys: del d[k]', "del inplace (drop_keys)")
timer("""d = dict.fromkeys(range(dsize))
{k:v for k,v in d.items() if k in keep_keys}""", "copy dict comprehension")
timer("""keep_keys=random.sample(range(dsize), 5)
d = dict.fromkeys(range(dsize))
{k:v for k,v in d.items() if k in keep_keys}""",
"copy dict comprehension - small keep_keys")


if __name__ == '__main__':
_time_filter_dict()

结果:

Empty statement 8.375600000000427e-05
pop inplace - exhaust iterator 1.046749841
pop inplace - exhaust iterator (drop_keys) 1.830537424
pop inplace - create list 1.1531293939999987
pop inplace - create list (drop_keys) 1.4512304149999995
del inplace 0.8008298079999996
del inplace (drop_keys) 1.1573763689999979
copy dict comprehension 1.1982901489999982
copy dict comprehension - small keep_keys 1.4407784069999998

因此,如果我们想就地更新,del似乎是赢家——当然,字典理解解决方案取决于正在创建的字典的大小,删除一半的密钥已经太慢了——所以如果可以就地过滤,请避免创建新的字典。

编辑以解决@mpen的评论-我从keep_keys计算了删除键(假设我们没有删除键)-我假设keep_keys/drop_keys是此迭代的集合,或者需要很长时间。有了这些假设,del仍然更快-但要确定的是:如果你有一个下降键的(集合、列表、元组),请选择del

如果您事先知道否定集(又名not键):

v = {'a': 'foo', 'b': 'bar', 'command': 'fizz', 'host': 'buzz'  }
args = {k: v[k] for k in v if k not in ["a", "b"]}
args # {'command': 'fizz', 'host': 'buzz'}