切割字典

136

我有一个字典,并且想把其中的一部分通过键的列表(或元组)传递给一个函数,就像这样:

# the dictionary
d = {1:2, 3:4, 5:6, 7:8}

# the subset of keys I'm interested in
l = (1,5)

现在,理想情况下我希望能够做到这一点:

>>> d[l]
{1:2, 5:6}

...但这并不奏效,因为它会查找与元组(1,5)相匹配的键,与d[1,5]相同。

d{1,5}甚至不是有效的Python代码(就我所知...),尽管它可能很方便:花括号暗示了一个无序集合或字典,因此返回一个包含指定键的字典对我来说看起来非常合理。

d[{1,5}]也有意义(“这里有一组键,给我匹配的项”),而且{1, 5}是一个不可哈希的集合,因此没有与之匹配的键 -- 但是当然它也会抛出一个错误。

我知道我可以这样做:

>>> dict([(key, value) for key,value in d.iteritems() if key in l])
{1: 2, 5: 6}

或者这样:

>>> dict([(key, d[key]) for key in l])

哪一种更紧凑,但我感觉一定有一种“更好”的方法来做到这一点。我是不是错过了一种更优雅的解决方案?

(我正在使用Python 2.7)


4
我认为理想的解决方案是 d[*l],但当然那行不通。 - Ken Williams
d[1, 5] 在 Python 中是有效的,无论是在 2.7 还是在 3.9(我刚在 3.x 中测试过),都可以正常工作。元组实际上并不需要括号 "(1, 5)"。在语法的某些地方,有点需要;例如,list.append 需要将单个元组参数括在括号中,但这只是删除了特殊情况的处理(如果给出多个参数,则 list.append 假定为元组,而不是引发 SyntaxError)。 - Jürgen A. Erhard
@JürgenA.Erhard d[1, 5] 只有在 d 是列表时才有效,而不是字典。除非当然你的字典中有一个键为 (1, 5) 的项:使用上述定义的 d,你会得到一个键错误。但如果你将其定义为 dt = {1: 2, 3: 4, 5: 6, (1, 5): 15},它可以很好地工作并产生 15 -- 但这绝对不是我想要的。目标是提供一个键的列表/元组/集合,并获取所有匹配的项。字典本质上是键/值对的集合。特别是自 Python 3.9 终于引入了 union 运算符后,交集和排除似乎是显而易见的补充。 - Zak
1
@Zak 你声称这不是有效的Python。实际上,它是完全有效的Python,因为编译器没有引发SyntaxError异常。 - Jürgen A. Erhard
1
@JürgenA.Erhard -- 是的,好的,你当然是正确的。这只是一种无效的从字典中检索多个项的方法。我已经更新了问题以更正此问题,并将花括号添加到不起作用的事物列表中,因为如果它们起作用,我会非常高兴的。 - Zak
13个回答

121

在Python 3中,你可以使用itertools的islice函数对dict.items()迭代器进行切片操作。

import itertools

d = {1: 2, 3: 4, 5: 6}

dict(itertools.islice(d.items(), 2))

{1: 2, 3: 4}

注意:此解决方案不考虑特定键。它按照d的内部排序进行切片,在Python 3.7+中保证按插入顺序排序。


2
那么...这会给我前两个元素,对吧?Python 3中的字典是否以某种方式排序?否则就无法确定它将返回哪些元素,并且它只能在连续的元素上工作,而我真正想要的是通过键列表选择它们。 - Zak
@Zak 在 Python 3.6+ 中,默认情况下字典是有序的,否则您需要使用 OrderedDict。 - Cesar Canassa
17
这个回答与问题没有真正的关联。 - Ken Williams
1
如果原始字典是字典的字典,则需要使用适用于Python3的答案,并且需要按顺序处理字典的字典。其他答案对我来说不适用,因为我希望将字典的字典作为有序字典的字典处理。 - weefwefwqg3
1
为了节省您几秒钟的时间:我们也可以使用 itertools.islice(iterable, start, stop[, step]),例如,list(islice('ABCDEFG', 2, 4)) 返回 ['C','D']。(来自文档。) - starriet
显示剩余2条评论

65
你应该遍历元组并检查键是否在字典中,而不是相反的方式。如果你不检查键是否存在并且它不在字典中,你将会得到一个键错误:

你应该遍历元组并检查键是否在字典中,而不是相反的方式。如果你不检查键是否存在并且它不在字典中,你将会得到一个键错误:

print({k:d[k] for k in l if k in d})

一些时间:

 {k:d[k] for k in set(d).intersection(l)}

In [22]: %%timeit                        
l = xrange(100000)
{k:d[k] for k in l}
   ....: 
100 loops, best of 3: 11.5 ms per loop

In [23]: %%timeit                        
l = xrange(100000)
{k:d[k] for k in set(d).intersection(l)}
   ....: 
10 loops, best of 3: 20.4 ms per loop

In [24]: %%timeit                        
l = xrange(100000)
l = set(l)                              
{key: d[key] for key in d.viewkeys() & l}
   ....: 
10 loops, best of 3: 24.7 ms per

In [25]: %%timeit                        

l = xrange(100000)
{k:d[k] for k in l if k in d}
   ....: 
100 loops, best of 3: 17.9 ms per loop
我不明白为什么{k:d[k] for k in l}不容易阅读和优雅,而且如果所有元素都在d中,那它非常高效。

5
感谢提供时间!"{k:d[k] for k in l}" 对于有一定经验的人来说已经足够易读(比我问题中稍微复杂的版本更易读),但像"d.intersect(l)"这样的写法可能更好:有一个字典,一个列表,我正在对它们进行操作,不需要三次提到k,因为k既不是操作的输入也不是输出。我知道我在非常高的层次上抱怨 :) - Zak
@Zak,别担心,我认为如果键始终在字典中,并且您为变量提供更多的解释性名称,那么{k:d[k] for k in l}就是相当Pythonic的。 - Padraic Cunningham
4
在这种情况下,我认为Perl和Ruby都提供了更加"优雅"的解决方案。 - GLRoman
1
为了在切片中每个键上节省一次查找,我遍历项目:{k:v for k,v in d.items() if k in l} - 这对我的性能有所改善,但可能因情况而异。 - F1Rumors
@F1Rumors 我猜这将取决于字典的长度与 l 的长度。对于只有几个项目的情况,查找可能会更快,但如果您仅省略了几个键,则遍历项目应该更快。我的字典足够小,我通常不担心速度。 - Zak

64
将字典切片,使用d.items()将其转换为元组的列表,对列表进行切片,并创建一个新的字典。
d = {1:2, 3:4, 5:6, 7:8}

获取前两个项目:
first_two = dict(list(d.items())[:2])

前两个:

{1: 2, 3: 4}

5
我喜欢这个答案。它恰好回答了我正在寻找的“切割字典”问题。 - Stefan
2
简单明了的答案,是最好的。+1 - Marcel Motta

37

使用集合在 dict.viewkeys() 字典视图上进行交集操作:

l = {1, 5}
{key: d[key] for key in d.viewkeys() & l}

这是 Python 2 的语法,在 Python 3 中请使用 d.keys()

这仍然使用循环,但至少字典推导式更易读。使用集合交集非常高效,即使 dl 很大。

演示:

>>> d = {1:2, 3:4, 5:6, 7:8}
>>> l = {1, 5}
>>> {key: d[key] for key in d.viewkeys() & l}
{1: 2, 5: 6}

1
{key:d[key] for key in l} 这段代码有问题吗? - itzMEonTV
1
如果你确定 l 中的所有键都在 d 中,那么这不是一个问题。但是使用 d.viewkeys() & l 取交集,即在 d 和集合 l 中都存在的键。 - Martijn Pieters
1
哦,明白了 :) {key:d[key] for key in l if key in d} 这个代码看起来很奇怪? - itzMEonTV
2
@itzmeontv:如果l很大而d很小怎么办?让Python自己创建交集,而不需要你自己循环。 - Martijn Pieters

12

编写一个 dict 的子类,它接受一个键列表作为“项”,并返回字典的“片段”:

class SliceableDict(dict):
    default = None
    def __getitem__(self, key):
        if isinstance(key, list):   # use one return statement below
            # uses default value if a key does not exist
            return {k: self.get(k, self.default) for k in key}
            # raises KeyError if a key does not exist
            return {k: self[k] for k in key}
            # omits key if it does not exist
            return {k: self[k] for k in key if k in self}
        return dict.get(self, key)

使用方法:

d = SliceableDict({1:2, 3:4, 5:6, 7:8})
d[[1, 5]]   # {1: 2, 5: 6}

如果你想为这种类型的访问使用单独的方法,可以使用*来接受任意数量的参数:

class SliceableDict(dict):
    def slice(self, *keys):
        return {k: self[k] for k in keys}
        # or one of the others from the first example

d = SliceableDict({1:2, 3:4, 5:6, 7:8})
d.slice(1, 5)     # {1: 2, 5: 6}
keys = 1, 5
d.slice(*keys)    # same

1
很酷的想法,不过我宁愿添加一个额外的属性而不是搞乱现有的函数。像 d.slice(l) 这样的东西。实际上,我一直希望有这样的东西存在。比循环更易读。 - Zak
当然,编写另一个方法是完全可行的,或者您可以使用__call__ - kindall
我不认为第一个建议是正确的。任何使用你的 d 的代码都会期望 [ 具有通常的语义,但它们已经改变了。slice 的例子是正确的。 - Ken Williams
1
@KenWilliams 好观点。也许更好的方法是传递一个列表。通常情况下,您不能将列表用作字典键,因此您不会失去任何功能。 - kindall
修改了第一个建议,使用列表。 - kindall
将列表作为参数传递给 __getitem__ 看起来很不错... 如果当前代码没有出现错误,那么更改这些语义将使有缺陷的代码“正常”运行。但如果一开始就有这个功能就好了,或者至少在早期的2.x版本中加入该功能。 - Jürgen A. Erhard

6

set intersectiondict comprehension 可以在这里使用。

# the dictionary
d = {1:2, 3:4, 5:6, 7:8}

# the subset of keys I'm interested in
l = (1,5)

>>>{key:d[key] for key in set(l) & set(d)}
{1: 2, 5: 6}

2

使用 operator.itemgetter

dict(zip(l, itemgetter(*l)(d)))

在线试用!


1
另一种选择是将字典转换为 pandas 的 Series 对象,然后定位指定的索引:
>>> d = {1:2, 3:4, 5:6, 7:8}
>>> l = [1,5]

>>> import pandas as pd
>>> pd.Series(d).loc[l].to_dict()
{1: 2, 5: 6}

这对手头的任务来说真是太重了。也许如果你已经在使用Pandas,并且键的列表已经是一个Pandas Series,并且字典很小的话,可能会好一些。Pandas真是太棒了,但它是一个庞大的库! - undefined

1

字典

d = {1:2, 3:4, 5:6, 7:8}

我感兴趣的键的子集

l = (1,5)

答案

{key: d[key] for key in l}

4
你好!这个答案已经在问题中提到了。请详细阐述你的答案,以使其更有意义。 - Rishab P
1
如果键是字符串,且有'n'对键值呢? - vijayraj34
@vijayraj34:关键是“l”必须是元组、列表、迭代器或类似的对象,无论它们的类型是什么。因此,如果“d={'b':1, 'd':2, 'f':3, 'h':8}”,并且您使用“l=('d', 'f')”,同样的方法也适用。 - Zak

1

dict(filter(lambda it: it[0] in l, d.items()))


1
感谢您对Stack Overflow社区做出贡献的兴趣。这个问题已经有很多答案了,其中一个答案已经得到社区广泛验证。您确定您的方法之前没有被提到过吗?如果是这样的话,能否解释一下您的方法与众不同的地方,在什么情况下您的方法可能更好,并且为什么您认为之前的答案不够满意。您可以编辑您的回答并提供解释吗? - Jeremy Caney

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