如何检查一个字典是否是另一个大字典的子集?

我尝试编写一个自定义过滤器方法,该方法接受任意数量的 Kwargs,并返回一个包含包含这些 Kwargs的类数据库列表元素的列表。

例如,假设 d1 = {'a':'2', 'b':'3'}d2 = 同样的东西。d1 == d2的结果为 True。但是假设 d2 = 同样的东西加上一堆其他的东西。我的方法需要能够判断 D2中的 d1是否存在,但 Python 不能对字典做到这一点。

背景:

我有一个 Word 类,每个对象都有类似 worddefinitionpart_of_speech等属性。我希望能够在这些单词的主列表中调用过滤器方法,比如 Word.objects.filter(word='jump', part_of_speech='verb-intransitive')。我不知道如何同时管理这些键和值。但是对于其他人来说,这可能在这个上下文之外具有更大的功能。

88572 次浏览

转换为项对并检查是否包含。

all(item in superset.items() for item in subset.items())

优化留给读者作为练习。

>>> d1 = {'a':'2', 'b':'3'}
>>> d2 = {'a':'2', 'b':'3','c':'4'}
>>> all((k in d2 and d2[k]==v) for k,v in d1.iteritems())
True

上下文:

>>> d1 = {'a':'2', 'b':'3'}
>>> d2 = {'a':'2', 'b':'3','c':'4'}
>>> list(d1.iteritems())
[('a', '2'), ('b', '3')]
>>> [(k,v) for k,v in d1.iteritems()]
[('a', '2'), ('b', '3')]
>>> k,v = ('a','2')
>>> k
'a'
>>> v
'2'
>>> k in d2
True
>>> d2[k]
'2'
>>> k in d2 and d2[k]==v
True
>>> [(k in d2 and d2[k]==v) for k,v in d1.iteritems()]
[True, True]
>>> ((k in d2 and d2[k]==v) for k,v in d1.iteritems())
<generator object <genexpr> at 0x02A9D2B0>
>>> ((k in d2 and d2[k]==v) for k,v in d1.iteritems()).next()
True
>>> all((k in d2 and d2[k]==v) for k,v in d1.iteritems())
True
>>>

键和值的检查使用: set(d1.items()).issubset(set(d2.items()))

如果你只需要检查钥匙: set(d1).issubset(set(d2))

我的函数也是为了同样的目的,递归地执行这个操作:

def dictMatch(patn, real):
"""does real dict match pattern?"""
try:
for pkey, pvalue in patn.iteritems():
if type(pvalue) is dict:
result = dictMatch(pvalue, real[pkey])
assert result
else:
assert real[pkey] == pvalue
result = True
except (AssertionError, KeyError):
result = False
return result

在您的示例中,即使 d2中包含其他内容,dictMatch(d1, d2)也应该返回 True,而且它也适用于较低的级别:

d1 = {'a':'2', 'b':{3: 'iii'}}
d2 = {'a':'2', 'b':{3: 'iii', 4: 'iv'},'c':'4'}


dictMatch(d1, d2)   # True

注意: 可能有更好的解决方案,避免 if type(pvalue) is dict子句,并适用于更广泛的情况(如哈希列表等)。此外,递归在这里不受限制,所以使用风险自负。;)

对于单元测试需要这个的人,请注意: 在 Python 的 TestCase类中还有一个 assertDictContainsSubset()方法。

Http://docs.python.org/2/library/unittest.html?highlight=assertdictcontainssubset#unittest : Testcase.assertDictContainsSubset

但是在3.2版本中已经被弃用了,不知道为什么,也许有其他的替代品。

这个函数适用于非散列值。我也认为它是清晰的,易于阅读。

def isSubDict(subDict,dictionary):
for key in subDict.keys():
if (not key in dictionary) or (not subDict[key] == dictionary[key]):
return False
return True


In [126]: isSubDict({1:2},{3:4})
Out[126]: False


In [127]: isSubDict({1:2},{1:2,3:4})
Out[127]: True


In [128]: isSubDict({1:{2:3}},{1:{2:3},3:4})
Out[128]: True


In [129]: isSubDict({1:{2:3}},{1:{2:4},3:4})
Out[129]: False

为了完整起见,你还可以这样做:

def is_subdict(small, big):
return dict(big, **small) == big

然而,我没有提出任何关于速度(或缺乏)或可读性(或缺乏)的主张。

更新: 正如 Boris 的评论所指出的那样,如果你使用 Python > = 3(或者换句话说: 面对任意类型的键,它只能在旧的 Python 2.x 中工作) ,那么这个技巧就不起作用了。

但是,如果您使用的是 Python 3.9或更新版本,那么您可以使它既可以使用非字符串类型的键,又可以获得更简洁的语法。

如果您的代码已经有两个字典作为变量,那么检查这个内联就非常简洁了:

if big | small == big:
# do something

否则,或者如果你喜欢上面提到的可重用功能,你可以这样做:

def is_subdict(small, big):
return big | small == big

工作原理与第一个函数相同,只是这一次使用了扩展为支持 dicts 的联合运算符。

在 Python3中,您可以使用 dict.items()来获得对 dictitem 的类似集合的视图。然后,您可以使用 <=操作符来测试一个视图是否是另一个视图的“子集”:

d1.items() <= d2.items()

在 Python 2.7中,使用 dict.viewitems()做同样的事情:

d1.viewitems() <= d2.viewitems()

在 Python 2.6及以下版本中,您将需要一个不同的解决方案,例如使用 all():

all(key in d2 and d2[key] == d1[key] for key in d1)

这个看似简单的问题花费了我几个小时的研究来找到一个100% 可靠的解决方案,所以我记录了我在这个答案中的发现。

  1. “ Python-ally”说起来,small_dict <= big_dict会是最直观的方式,但可惜的是它 没用的{'a': 1} < {'a': 1, 'b': 2}似乎可以在 Python2中工作,但是它不可靠,因为官方文档明确地调用了它。搜索“除了等式以外的结果一致地被解析,但是没有另外定义。”在 这部分。更不用说,比较 Python3中的2个 dicts 会导致 TypeError 异常。

  2. 第二个最直观的东西是仅用于 Python 2.7的 small.viewitems() <= big.viewitems()和用于 Python 3的 small.items() <= big.items()。但有一点需要注意: 它是 可能有问题。如果您的程序可能被用于 Python < = 2.6,那么它的 d1.items() <= d2.items()实际上是在比较2个元组列表,没有特定的顺序,因此最终结果将是不可靠的,并且在您的程序中成为一个讨厌的错误。我并不热衷于为 Python < = 2.6编写另一个实现,但是我仍然不喜欢我的代码带有一个已知的 bug (即使它是在一个不受支持的平台上)。所以我放弃了这个方法。

  3. 我在 @ blubberdiblub 的回答公司安顿下来(这是他的功劳) :

    Def is _ subdict (小,大) : 返回结果(big,* * small) = = big

    值得指出的是,这个答案依赖于字母之间的 ==行为,这在官方文件中有明确的定义,因此 应该在每个 Python 版本中都可以使用。搜索:

    • 这一页中的最后一句是“字典只有在有相同的(键,值)对时才相等”
    • ”映射(dict 的实例)比较相等的当且仅当它们具有相等的(键,值)对。键和元素的相等比较强化了反身性。”在 这一页

下面是针对这个问题的一个通用递归解决方案:

import traceback
import unittest


def is_subset(superset, subset):
for key, value in subset.items():
if key not in superset:
return False


if isinstance(value, dict):
if not is_subset(superset[key], value):
return False


elif isinstance(value, str):
if value not in superset[key]:
return False


elif isinstance(value, list):
if not set(value) <= set(superset[key]):
return False
elif isinstance(value, set):
if not value <= superset[key]:
return False


else:
if not value == superset[key]:
return False


return True




class Foo(unittest.TestCase):


def setUp(self):
self.dct = {
'a': 'hello world',
'b': 12345,
'c': 1.2345,
'd': [1, 2, 3, 4, 5],
'e': {1, 2, 3, 4, 5},
'f': {
'a': 'hello world',
'b': 12345,
'c': 1.2345,
'd': [1, 2, 3, 4, 5],
'e': {1, 2, 3, 4, 5},
'g': False,
'h': None
},
'g': False,
'h': None,
'question': 'mcve',
'metadata': {}
}


def tearDown(self):
pass


def check_true(self, superset, subset):
return self.assertEqual(is_subset(superset, subset), True)


def check_false(self, superset, subset):
return self.assertEqual(is_subset(superset, subset), False)


def test_simple_cases(self):
self.check_true(self.dct, {'a': 'hello world'})
self.check_true(self.dct, {'b': 12345})
self.check_true(self.dct, {'c': 1.2345})
self.check_true(self.dct, {'d': [1, 2, 3, 4, 5]})
self.check_true(self.dct, {'e': {1, 2, 3, 4, 5}})
self.check_true(self.dct, {'f': {
'a': 'hello world',
'b': 12345,
'c': 1.2345,
'd': [1, 2, 3, 4, 5],
'e': {1, 2, 3, 4, 5},
}})
self.check_true(self.dct, {'g': False})
self.check_true(self.dct, {'h': None})


def test_tricky_cases(self):
self.check_true(self.dct, {'a': 'hello'})
self.check_true(self.dct, {'d': [1, 2, 3]})
self.check_true(self.dct, {'e': {3, 4}})
self.check_true(self.dct, {'f': {
'a': 'hello world',
'h': None
}})
self.check_false(
self.dct, {'question': 'mcve', 'metadata': {'author': 'BPL'}})
self.check_true(
self.dct, {'question': 'mcve', 'metadata': {}})
self.check_false(
self.dct, {'question1': 'mcve', 'metadata': {}})


if __name__ == "__main__":
unittest.main()

注意: 原始代码在某些情况下会失败,修理的学分归 @ Olivier-Melanie

我知道这个问题已经过时了,但这里是我的解决方案,用于检查一个嵌套词典是否是另一个嵌套词典的一部分。解决方案是递归的。

def compare_dicts(a, b):
for key, value in a.items():
if key in b:
if isinstance(a[key], dict):
if not compare_dicts(a[key], b[key]):
return False
elif value != b[key]:
return False
else:
return False
return True

如果你不介意使用 pydash,那里有 is_match,它完全可以做到这一点:

import pydash


a = {1:2, 3:4, 5:{6:7}}
b = {3:4.0, 5:{6:8}}
c = {3:4.0, 5:{6:7}}


pydash.predicates.is_match(a, b) # False
pydash.predicates.is_match(a, c) # True

一个针对嵌套字典的简短的递归实现:

def compare_dicts(a,b):
if not a: return True
if isinstance(a, dict):
key, val = a.popitem()
return isinstance(b, dict) and key in b and compare_dicts(val, b.pop(key)) and compare_dicts(a, b)
return a == b

这将消耗 a 和 b 字母。如果有人知道一个避免这种情况的好方法,而不像在其他答案中那样求助于部分迭代解决方案,请告诉我。我需要一个方法,分裂成头部和尾部的基础上,一个关键的字典。

这段代码作为编程练习更有用,而且可能比这里混合了递归和迭代的其他解决方案慢得多。@ 胡桃夹子的解决方案对于嵌套字典来说是相当不错的。

这里有一个解决方案,它还可以正确递归到字典中包含的列表和集中。您也可以使用这个列表包含字典等..。.

def is_subset(subset, superset):
if isinstance(subset, dict):
return all(key in superset and is_subset(val, superset[key]) for key, val in subset.items())
    

if isinstance(subset, list) or isinstance(subset, set):
return all(any(is_subset(subitem, superitem) for superitem in superset) for subitem in subset)


# assume that subset is a plain value if none of the above match
return subset == superset

在使用 python 3.10时,可以使用 python 的新 match 语句进行类型检查:

def is_subset(subset, superset):
match subset:
case dict(_): return all(key in superset and is_subset(val, superset[key]) for key, val in subset.items())
case list(_) | set(_): return all(any(is_subset(subitem, superitem) for superitem in superset) for subitem in subset)
# assume that subset is a plain value if none of the above match
case _: return subset == superset

使用这个提供部分比较和漂亮差异的包装器对象:


class DictMatch(dict):
""" Partial match of a dictionary to another one """
def __eq__(self, other: dict):
assert isinstance(other, dict)
return all(other[name] == value for name, value in self.items())


actual_name = {'praenomen': 'Gaius', 'nomen': 'Julius', 'cognomen': 'Caesar'}
expected_name = DictMatch({'praenomen': 'Gaius'})  # partial match
assert expected_name == actual_name  # True

大多数的答案将不工作,如果在 dict 有一些其他的 dicts 数组,这里有一个解决方案:

def d_eq(d, d1):
if not isinstance(d, (dict, list)):
return d == d1
if isinstance(d, list):
return all(d_eq(a, b) for a, b in zip(d, d1))
return all(d.get(i) == d1[i] or d_eq(d.get(i), d1[i]) for i in d1)


def is_sub(d, d1):
if isinstance(d, list):
return any(is_sub(i, d1) for i in d)
return d_eq(d, d1) or (isinstance(d, dict) and any(is_sub(b, d1) for b in d.values()))


print(is_sub(dct_1, dict_2))

取自 如何检查授权词典是否是另一个复杂授权词典的子集

另一种方法是:

>>> d1 = {'a':'2', 'b':'3'}
>>> d2 = {'a':'2', 'b':'3','c':'4'}
>>> d3 = {'a':'1'}
>>> set(d1.items()).issubset(d2.items())
True
>>> set(d3.items()).issubset(d2.items())
False

对于 Python 3.9,我是这样使用的:

def dict_contains_dict(small: dict, big: dict):
return (big | small) == big