Python:检查一个字典是否是另一个较大字典的子集

时间:2012-02-17 06:15:30

标签: python dictionary filter subset

我正在尝试编写一个自定义过滤器方法,它接受任意数量的 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')。我无法弄清楚如何同时管理这些键和值。但是,对于其他人来说,这可能会在此背景下具有更大的功能。

18 个答案:

答案 0 :(得分:85)

转换为项目对并检查包含。

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

优化留给读者练习。

答案 1 :(得分:57)

在Python 3中,您可以使用dict.items()获取dict项目的类似集合的视图。然后,您可以使用<=运算符来测试一个视图是否是另一个视图的“子集”:

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)

答案 2 :(得分:31)

请注意那些需要单元测试的人:Python的assertDictContainsSubset()类中还有一个TestCase方法。

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

然而,它在3.2中被弃用,不知道为什么,也许有替代它。

答案 3 :(得分:20)

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

如果您只需要检查键:         set(d1).issubset(set(d2))

答案 4 :(得分:15)

为完整起见,您也可以这样做:

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

但是,我对速度(或缺乏速度)或可读性(或缺乏速度)没有任何声明。

答案 5 :(得分:10)

>>> 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
>>>

答案 6 :(得分:4)

我的功能是出于同样的目的,递归地执行此操作:

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子句,并适用于更广泛的案例(如哈希列表等)。此外递归不限于此,因此使用风险自负。 ;)

答案 7 :(得分:2)

这是给出问题的一般递归解决方案:

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()

注意:原始代码在某些情况下会失败,fixing的信用转为@olivier-melançon

答案 8 :(得分:2)

这里是一种解决方案,也可以正确地递归到词典中包含的列表和集合中。您也可以将其用于包含字典等的列表...

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

答案 9 :(得分:2)

我知道这个问题很旧,但是这是我的解决方案,用于检查一个嵌套字典是否是另一个嵌套字典的一部分。解决方案是递归的。

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

答案 10 :(得分:2)

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

  1. &#34; Python化烯丙基&#34;说起来,small_dict <= big_dict将是最直观的方式,但太糟糕了,赢得了{'a': 1} < {'a': 1, 'b': 2}似乎在Python 2中有效,但它不可靠,因为官方文档明确地将其命名。去搜索&#34;除了相等之外的结果一致地解决,但没有另外定义。&#34;在this section。更不用说,比较Python 3中的2个dicts会导致TypeError异常。

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

  3. 我和@blubberdiblub 's answer安定下来(信用证告诉他):

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

    值得指出的是,这个答案依赖于dicts之间的==行为,这在官方文档中有明确定义,因此应该适用于每个Python版本。去搜索:

    • &#34;当且仅当它们具有相同(键,值)对时,字典才会相等。&#34;是this page
    • 中的最后一句话
    • &#34;映射(dict的实例)比较等于if和仅当它们具有相等(键,值)对时。键和元素的相等比较强制反身性。&#34;在this page

答案 11 :(得分:1)

另一种方法:

>>> 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

答案 12 :(得分:1)

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

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

答案 13 :(得分:0)

如果您不介意使用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






答案 14 :(得分:0)

适用于嵌套字典的简短递归实现:

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字典。如果有人知道避免这种情况的好方法,而又不像其他答案那样求助于部分迭代的解决方案,请告诉我。我需要一种基于键将字典拆分为头部和尾部的方法。

此代码作为编程练习更有用,并且可能比此处混合递归和迭代的其他解决方案慢很多。 @Nutcracker's solution非常适合嵌套字典。

答案 15 :(得分:0)

此函数适用于不可散列的值。我也认为它清晰易读。

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

答案 16 :(得分:0)

使用此包装器对象提供部分比较和出色的差异:


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

答案 17 :(得分:0)

如果在字典中有一些其他字典数组,则大多数答案将不起作用,这是解决此问题的方法:

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))

取自How to check if dict is subset of another complex dict