如何在Python字典上执行集合操作?

7
虽然能在字典的键之间执行集合操作非常有用,但我经常希望能在字典本身上执行这些操作。我找到了一些关于两个字典的差异的方法,但它们很冗长,我觉得应该有更符合Python语法的答案。
5个回答

9

简洁版:使用代码{k:d1.get(k, k in d1 or d2[k]) for k in set(d1) | set(d2)},其中|可以替换任何其他集合运算符。

根据@torek的评论,另一个易于记忆(同时完全通用)的方法是:{k:d1.get(k,d2.get(k)) for k in set(d1) | set(d2)}

详细回答如下:

我的第一个回答没有正确处理计算为False的值。以下是改进版本,可以处理Falsey值:

>>> d1 = {'one':1, 'both':3, 'falsey_one':False, 'falsey_both':None}
>>> d2 = {'two':2, 'both':30, 'falsey_two':None, 'falsey_both':False}
>>> 
>>> print "d1 - d2:", {k:d1[k] for k in d1 if k not in d2}                  # 0
d1 - d2: {'falsey_one': False, 'one': 1}
>>> print "d2 - d1:", {k:d2[k] for k in d2 if k not in d1}                  # 1
d2 - d1: {'falsey_two': None, 'two': 2}
>>> print "intersection:", {k:d1[k] for k in d1 if k in d2}                      # 2
intersection: {'both': 3, 'falsey_both': None}
>>> print "union:", {k:d1.get(k, k in d1 or d2[k]) for k in set(d1) | set(d2)}   # 3
union: {'falsey_one': False, 'falsey_both': None, 'both': 3, 'two': 2, 'one': 1, 'falsey_two': None}
< p > < code > union 的版本是最通用的,可以转换为函数:
>>> def dict_ops(d1, d2, setop):
...     """Apply set operation `setop` to dictionaries d1 and d2
... 
...     Note: In cases where values are present in both d1 and d2, the value from
...     d1 will be used.
...     """
...     return {k:d1.get(k,k in d1 or d2[k]) for k in setop(set(d1), set(d2))}
... 
>>> print "d1 - d2:", dict_ops(d1, d2, lambda x,y: x-y)
d1 - d2: {'falsey_one': False, 'one': 1}
>>> print "d2 - d1:", dict_ops(d1, d2, lambda x,y: y-x)
d2 - d1: {'falsey_two': None, 'two': 2}
>>> import operator as op
>>> print "intersection:", dict_ops(d1, d2, op.and_)
intersection: {'both': 3, 'falsey_both': None}
>>> print "union:", dict_ops(d1, d2, op.or_)
union: {'falsey_one': False, 'falsey_both': None, 'both': 3, 'two': 2, 'one': 1, 'falsey_two': None}

如果两个字典中都包含某个项目,将使用d1中的值。当然,通过改变函数参数的顺序,我们也可以返回d2中的值。

>>> print "union:", dict_ops(d2, d1, op.or_)
union: {'both': 30, 'falsey_two': None, 'falsey_one': False, 'two': 2, 'one': 1, 'falsey_both': False}

嘿,如果k不在d1中,则将k in d1 or d2[k]作为默认值相当酷:它避免了在kd1中时精确评估d2[k],因此不需要d1.get的第二个参数 :-)(请注意,d2.get(k)也可以工作,但需要查找d2;最终效率是否真的更低我不确定。) - torek
@torek 谢谢。根据您所说的,以下代码也可以运行,并且可能是最容易记住的:{k:d1.get(k,d2.get(k)) for k in set(d1) | set(d2)} - snth
@snth 很棒的回答!有一个问题:当您的函数输入为op.or_lambda x,y:x-y时,这部分代码setop(set(d1),set(d2))是如何工作的? - gython
1
@gython 这些运算符是为集合定义的,因此它们可以直接使用。如果您想了解更多细节,请查阅有关集合的相关文档。 - snth
@snth 谢谢,那很有帮助!最后一个问题:关于这部分 (k,k in d1 or d2[k])?为什么需要单独的 k,以及 k in d1d1[k] 在功能上有什么区别?谢谢! - gython
你真正想要的是 d1.get(k,d2[k]),但问题在于如果 kd1 中但不在 d2 中,则会得到一个 IndexErrorLookupError,因为函数参数在调用函数之前被求值。所以 k in d1 的部分是为了防止这种情况发生,因为短路 or 运算符将防止在 kd1 中的情况下评估第二个表达式。 - snth

3

编辑:这里的配方不能正确处理False值。我已经提交了另一个改进的答案。

以下是我想出的一些配方:

>>> d1 = {'one':1, 'both':3}
>>> d2 = {'two':2, 'both':30}
>>> 
>>> print "d1 only:", {k:d1.get(k) or d2[k] for k in set(d1) - set(d2)}     # 0
d1 only: {'one': 1}
>>> print "d2 only:", {k:d1.get(k) or d2[k] for k in set(d2) - set(d1)}     # 1
d2 only: {'two': 2}
>>> print "in both:", {k:d1.get(k) or d2[k] for k in set(d1) & set(d2)}     # 2
in both: {'both': 3}
>>> print "in either:", {k:d1.get(k) or d2[k] for k in set(d1) | set(d2)}   # 3
in either: {'both': 3, 'two': 2, 'one': 1}

虽然#0和#2中的表达式可以更简单,但我喜欢这个表达式的普遍性,因为它允许我复制并粘贴到任何地方,并仅更改结尾处所需的集合操作。

当然,我们可以将其转化为一个函数:

>>> def dict_ops(d1, d2, setop):
...     return {k:d1.get(k) or d2[k] for k in setop(set(d1), set(d2))}
... 
>>> print "d1 only:", dict_ops(d1, d2, lambda x,y: x-y)
d1 only: {'one': 1}
>>> print "d2 only:", dict_ops(d1, d2, lambda x,y: y-x)
d2 only: {'two': 2}
>>> import operator as op
>>> print "in both:", dict_ops(d1, d2, op.and_)
in both: {'both': 3}
>>> print "in either:", dict_ops(d1, d2, op.or_)
in either: {'both': 3, 'two': 2, 'one': 1}
>>> print "in either:", dict_ops(d2, d1, lambda x,y: x|y)
in either: {'both': 30, 'two': 2, 'one': 1}

注意一下 d1[k] 存在但 bool(d1[k])False 的情况,例如,如果 d1['both'] = 0,你会得到 d2['both']。这似乎是完全有效的——如果它在两个字典中都存在,哪个值才是“正确”的呢?——但如果你期望从 d1 中获取值,并且通常你也是从 d1 中获取值,那么这可能会让你感到惊讶。 - torek
如果您将操作标记为等效集合操作(如并集、交集、差集等),那么您的答案将更有用。 - martineau
@torek,你说的关于False值的问题是正确的。我提交了一个新答案,希望能够正确地处理这些问题。我没有编辑这个答案,因为我认为新答案与原来的答案有很大不同,而且人们已经对它进行了投票。 - snth
@martineau 谢谢,我已经在我的新答案中重新标记了输出。 - snth

2

这是一个旧问题,但我想强调一下我的ubelt包(https://github.com/Erotemic/ubelt),其中包含了解决这个问题的方案。

我不确定为什么PEP584只添加了联合操作而没有其他集合操作到字典中。我需要更深入地研究一下,看看是否存在任何现有理性,解释为什么Python字典默认情况下不包含这些方法(我想必须有,我不明白开发人员如何不知道字典上的集合操作)。

但是重点是,Ubelt实现了这些核心字典集合操作的函数:

我希望有一天这些或类似的方法被添加到Python字典本身。


0

你可以使用funcy

>>> import funcy
>>> a = {1: 2, 3: 4}
>>> b = {3: 5, 6: 8}
>>> funcy.merge(a, b)
{1: 2, 3: 5, 6: 8}
>>> funcy.project(a, b)
{3: 4}
>>> funcy.omit(a, b)
{1: 2}
>>> a, b
({1: 2, 3: 4}, {3: 5, 6: 8})

-1
这是一些更多内容: 设置加法 d1 + d2
{key: value for key, value in d1.items() + d2.items()}
# here values that are present in `d1` are replaced by values in `d2`

或者,

d3 = d1.copy()
d3.update(d2)

集合差 d1 - d2

{key: value for key, value in d1.items() if key not in d2}

你的差集就是集合交集。 - Asterios

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