从字典中提取一部分键值对?

458

我有一个大字典对象,其中有几对键值对(大约16个),但我只对其中的3个感兴趣。如何最好地(最短/高效/最优雅)对这样的字典进行子集操作?

我所知道的最好方法是:

bigdict = {'a':1,'b':2,....,'z':26} 
subdict = {'l':bigdict['l'], 'm':bigdict['m'], 'n':bigdict['n']}

我确信有比这更优雅的方法。

14个回答

599

你可以尝试:

dict((k, bigdict[k]) for k in ('l', 'm', 'n'))

...或在Python版本2.7或更高版本中 Python 3 (感谢Fábio Diniz指出它也适用于2.7):

{k: bigdict[k] for k in ('l', 'm', 'n')}

更新:正如Håvard S指出的那样,我假设您知道字典中的键将存在于其中。如果您无法做出这种假设,请参阅他的答案。或者,如timbo在评论中指出的那样,如果您希望在bigdict中缺少的键映射到None,则可以执行以下操作:

{k: bigdict.get(k, None) for k in ('l', 'm', 'n')}

如果您使用的是Python 3,并且您希望新字典中仅包含原始字典中实际存在的键,您可以利用事实来查看对象实现了某些集合操作:

{k: bigdict[k] for k in bigdict.keys() & {'l', 'm', 'n'}}

8
如果 bigdict 不包含 k,则会失败。 - Håvard S
13
{k: bigdict.get(k,None) for k in ('l', 'm', 'n')} 这段代码将会处理源字典中缺失指定键的情况,通过在新字典中将该键设为None来解决。 - timbo
9
根据使用情况,{k: bigdict[k] for k in ('l','m','n') if k in bigdict}可能更好,因为它仅存储实际具有值的键。 - Brian Wylie
6
bigdict.keys() & {'l', 'm', 'n'} 在 Python2.7 中应该改为 bigdict.viewkeys() & {'l', 'm', 'n'} - kxr
6
如果我的dict太大怎么办? - Adamantish
显示剩余10条评论

151

稍微简短一些,至少:

wanted_keys = ['l', 'm', 'n'] # The keys you want
dict((k, bigdict[k]) for k in wanted_keys if k in bigdict)

12
如果大字典中没有某个键,与将其设为None不同,排除该键的替代行为应获得+1。 - dhj
1
如果你必须要所有的键,可以使用 dict((k,bigdict.get(k,defaultVal) for k in wanted_keys) 的方式。或者: - Thomas Andrews
4
这个答案被一个“t”所保存。 - sakurashinken
2
你的解决方案还有一个更简短的语法变体,即使用“{}”,即“{k:bigdict [k] for k in wanted_keys if k in bigdict}”。 - Arty

37

所有方法的速度比较:

更新于2020.07.13(感谢@user3780389):仅适用于bigdict中的键。

 IPython 5.5.0 -- An enhanced Interactive Python.
Python 2.7.18 (default, Aug  8 2019, 00:00:00) 
[GCC 7.3.1 20180303 (Red Hat 7.3.1-5)] on linux2
import numpy.random as nprnd
  ...: keys = nprnd.randint(100000, size=10000)
  ...: bigdict = dict([(_, nprnd.rand()) for _ in range(100000)])
  ...: 
  ...: %timeit {key:bigdict[key] for key in keys}
  ...: %timeit dict((key, bigdict[key]) for key in keys)
  ...: %timeit dict(map(lambda k: (k, bigdict[k]), keys))
  ...: %timeit {key:bigdict[key] for key in set(keys) & set(bigdict.keys())}
  ...: %timeit dict(filter(lambda i:i[0] in keys, bigdict.items()))
  ...: %timeit {key:value for key, value in bigdict.items() if key in keys}
100 loops, best of 3: 2.36 ms per loop
100 loops, best of 3: 2.87 ms per loop
100 loops, best of 3: 3.65 ms per loop
100 loops, best of 3: 7.14 ms per loop
1 loop, best of 3: 577 ms per loop
1 loop, best of 3: 563 ms per loop

正如预期的那样:字典推导是最佳选项。


1
前三个操作与最后两个操作不同,如果keybigdict中不存在,将导致错误。 - naught101
3
不错,也许值得加入{key:bigdict[key] for key in bigdict.keys() & keys}这个被接受的解决方法中,它实现了过滤功能,并且在我的机器上实际上比你列出的第一种方法更快,因为第一种方法没有进行过滤。事实上,对于这些非常大的键集,{key:bigdict[key] for key in set(keys) & set(bigdict.keys())}似乎更快一些... - teichert
@telchert,你忽略了一个问题,在给出速度比较时,bigdict.keys()和keys不是集合。而且,通过显式转换为集合,接受的解决方案并不那么快。 - Sklavit
@kriss,“丑陋的”解决方案指的是什么?指硬编码的密钥吗?固然,使用硬编码的内容来解决问题会更快,毫无疑问。 - Sklavit
对于大量的键,它并不更快。但这也可能是测试设置的某些人为因素,因为这是唯一一个案例代码使用 eval 的度量(在生产代码中不需要)。 - kriss
显示剩余5条评论

30
interesting_keys = ('l', 'm', 'n')
subdict = {x: bigdict[x] for x in interesting_keys if x in bigdict}

@loutre,你还有什么其他建议来确保你提取了所有给定键的数据? - theheadofabroom
1
抱歉,我犯了一个错误。我以为你在“bigdict”上循环。我的错。我删除了我的评论。 - loutre

18

这个答案使用类似于所选答案的字典推导式,但不会在缺少项目时引发异常。

Python 2 版本:

{k:v for k, v in bigDict.iteritems() if k in ('l', 'm', 'n')}

Python 3 版本:

{k:v for k, v in bigDict.items() if k in ('l', 'm', 'n')}

5
但是如果大字典非常大,它仍然会完全迭代(这是一个O(n)操作),而反向字典只需要获取3个项(每个都是O(1)操作)。 - wouter bolsterlee
1
这个问题是关于一个仅有16个键的字典。 - Meow

8
如果您想保留大部分键,同时删除一些键,可以采用另一种方法:
{k: bigdict[k] for k in bigdict.keys() if k not in ['l', 'm', 'n']}

4
更简洁的写法: {k: v for k, v in bigdict.items() if k not in {'l', 'm', 'n'}} - pierresegonne

7
也许:
subdict=dict([(x,bigdict[x]) for x in ['l', 'm', 'n']])

Python 3甚至支持以下内容:

subdict={a:bigdict[a] for a in ['l','m','n']}

请注意,您可以按照以下方式在字典中检查是否存在:

请注意,您可以按照以下方式在字典中检查是否存在:

subdict=dict([(x,bigdict[x]) for x in ['l', 'm', 'n'] if x in bigdict])

针对 Python 3 的回复:

subdict={a:bigdict[a] for a in ['l','m','n'] if a in bigdict}

如果a不在bigdict中,则失败。 - Håvard S
那些只被认为在Python 3中可行的事情,在2.7版本中同样适用。 - Clint Eastwood

6

您也可以使用map函数(这是一个非常有用的函数,值得学习):

sd = dict(map(lambda k: (k, l.get(k, None)), l))

示例:

large_dictionary = {'a1':123, 'a2':45, 'a3':344}
list_of_keys = ['a1', 'a3']
small_dictionary = dict(map(lambda key: (key, large_dictionary.get(key, None)), list_of_keys))

附注:我从之前的回答中借用了.get(key, None)


4

好的,这是我几次烦恼的事情,所以谢谢Jayesh提出了这个问题。

以上的答案似乎都是一个很好的解决方案,但如果您在代码中多处使用此功能,则封装该功能是有意义的。此外,这里有两种可能的用例:一种是您关心所有关键字是否在原始字典中,另一种则不在意。最好平等地对待这两种情况。

因此,我建议编写字典的子类,例如:

class my_dict(dict):
    def subdict(self, keywords, fragile=False):
        d = {}
        for k in keywords:
            try:
                d[k] = self[k]
            except KeyError:
                if fragile:
                    raise
        return d

Now you can pull out a sub-dictionary with

orig_dict.subdict(keywords)

用法示例:

#
## our keywords are letters of the alphabet
keywords = 'abcdefghijklmnopqrstuvwxyz'
#
## our dictionary maps letters to their index
d = my_dict([(k,i) for i,k in enumerate(keywords)])
print('Original dictionary:\n%r\n\n' % (d,))
#
## constructing a sub-dictionary with good keywords
oddkeywords = keywords[::2]
subd = d.subdict(oddkeywords)
print('Dictionary from odd numbered keys:\n%r\n\n' % (subd,))
#
## constructing a sub-dictionary with mixture of good and bad keywords
somebadkeywords = keywords[1::2] + 'A'
try:
    subd2 = d.subdict(somebadkeywords)
    print("We shouldn't see this message")
except KeyError:
    print("subd2 construction fails:")
    print("\toriginal dictionary doesn't contain some keys\n\n")
#
## Trying again with fragile set to false
try:
    subd3 = d.subdict(somebadkeywords, fragile=False)
    print('Dictionary constructed using some bad keys:\n%r\n\n' % (subd3,))
except KeyError:
    print("We shouldn't see this message")

如果您运行了上面的所有代码,您应该看到以下输出(格式可能会有所不同):
原始字典: {'a':0,'c':2,'b':1,'e':4,'d':3,'g':6,'f':5, 'i': 8, 'h': 7, 'k': 10, 'j': 9, 'm': 12, 'l': 11, 'o': 14, 'n': 13, 'q': 16, 'p': 15, 's': 18, 'r': 17, 'u': 20, 't': 19, 'w': 22, 'v': 21, 'y': 24, 'x': 23, 'z': 25}
从奇数键创建的字典: {'a':0,'c':2,'e':4,'g':6,'i': 8,'k':10,'m':12,'o':14,'q':16,'s':18,'u':20,'w':22,'y':24}
subd2构造失败: 原始字典中不包含某些键
使用一些错误键构建的字典: {'b':1,'d':3,'f':5,'h':7,'j':9,'l':11,'n':13,'p':15,'r':17,'t':19,'v':21,'x':23,'z':25}

1
子类化需要将现有的字典对象转换为子类类型,这可能会很昂贵。为什么不直接编写一个简单的函数 subdict(orig_dict, keys, ...) - musiphil
@musiphil:我怀疑开销差别不大。子类化的好处是方法是类的一部分,不需要导入或内联。这个答案中代码的唯一潜在问题或限制是结果不是类型为my_dict - martineau

2

这是另一种方法(我更喜欢Mark Longair的回答)

di = {'a':1,'b':2,'c':3}
req = ['a','c','w']
dict([i for i in di.iteritems() if i[0] in di and i[0] in req])

对于大字典来说,速度很慢。 - kxr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接