Python: проверьте, является ли один словарь подмножеством другого более крупного словаря


Я пытаюсь написать пользовательский метод filter, который принимает произвольное число kwargs и возвращает список, содержащий элементы списка, подобного базе данных, которые содержат эти kwargs.

например,d1 = {'a':'2', 'b':'3'} и d2 = то же самое. d1 == d2 результаты в True. Но предположим, что d2 = то же самое плюс куча других вещей. Мой метод должен быть в состоянии сказать, если d1 в d2, но Python не может этого сделать словари.

контекст:

у меня есть класс Word, и каждый объект имеет свойства, такие как word,definition,part_of_speech и так далее. Я хочу иметь возможность вызывать метод фильтра в основном списке этих слов, например Word.objects.filter(word='jump', part_of_speech='verb-intransitive'). Я не могу понять, как управлять этими ключами и значениями одновременно. Но это может иметь большую функциональность вне этого контекста для других людей.

11 63

11 ответов:

преобразовать в пары элементов и проверить на сдерживание.

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

оптимизация остается в качестве упражнения для читателя.

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

Примечание для людей, которые нуждаются в этом для модульного тестирования: там же assertDictContainsSubset() метод в Python это TestCase класса.

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

однако он устарел в 3.2, не знаю почему, может быть, есть замена для него.

для проверки ключей и значений используйте: set(d1.items()).issubset(set(d2.items()))

Если вам нужно проверить только ключи: set(d1).issubset(set(d2))

для полноты картины, вы также можете сделать это:

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

однако я не предъявляю никаких претензий относительно скорости (или ее отсутствия) или читаемости (или ее отсутствия).

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

моя функция для той же цели, делаем это рекурсивно:

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

в вашем примере dictMatch(d1, d2) должен возвращать True, даже если d2 имеет другие вещи в нем, плюс это относится и к более низким уровням:

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 предложение и применяется к еще более широкому кругу случаев (например, списки хэшей и т. д.). Также рекурсия здесь не ограничена, поэтому используйте на свой страх и риск. ;)

эта, казалось бы, простая проблема стоит мне пару часов в исследованиях, чтобы найти 100% надежное решение, поэтому я задокументировал то, что я нашел в этом ответе.

  1. "весть-союзник" говорит, small_dict <= big_dict было бы самым интуитивным способом, но слишком плохо, что это не работает. {'a': 1} < {'a': 1, 'b': 2} по-видимому, работает в Python 2, но это не является надежным, потому что официальный документ явно вызывает его. Перейти к поиску " результаты, отличные от равенства, разрешены последовательно, но не определены."в в этом разделе. Не говоря уже о том, что сравнение 2 dicts в Python 3 приводит к исключению TypeError.

  2. вторая наиболее интуитивная вещь small.viewitems() <= big.viewitems() только для Python 2.7, и small.items() <= big.items() для Python 3. Но есть один нюанс: это потенциально багги. Если ваша программа потенциально может быть использована на Python d1.items() <= d2.items() на самом деле сравнивать 2 списка кортежей, без определенного порядка, так конечный результат будет ненадежным, и это станет неприятной ошибкой в вашей программе. Я не стремлюсь писать еще одну реализацию для Python

  3. Я остаюсь с @blubberdiblub ' s ответ (кредит идет к нему):

    def is_subdict(small, big): return dict(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-melançon

эта функция работает не hashable значения. Я также думаю, что это ясно и легко читать.

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