Python: проверьте, является ли один словарь подмножеством другого более крупного словаря - PullRequest
78 голосов
/ 17 февраля 2012

Я пытаюсь написать собственный метод фильтра, который принимает произвольное число 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').Я не могу понять, как управлять этими ключами и значениями одновременно.Но это может иметь большую функциональность вне этого контекста для других людей.

Ответы [ 12 ]

86 голосов
/ 17 февраля 2012

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

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

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

62 голосов
/ 11 января 2017

В 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)
31 голосов
/ 07 октября 2013

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

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

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

20 голосов
/ 12 августа 2012

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

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

15 голосов
/ 15 февраля 2016

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

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

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

10 голосов
/ 17 февраля 2012
>>> 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
>>>
4 голосов
/ 20 августа 2013

Моя функция для той же цели, выполняющая это рекурсивно:

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

2 голосов
/ 27 августа 2018

Я знаю, что этот вопрос старый, но вот мое решение для проверки, является ли один вложенный словарь частью другого вложенного словаря.Решение рекурсивное.

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
2 голосов
/ 22 марта 2018

Вот общее рекурсивное решение для данной задачи:

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

2 голосов
/ 18 февраля 2017

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

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

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

  3. Я согласен с ответом @ blubberdiblub (Кредит ему предоставляется):

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

    Стоит отметить, что этот ответ основан на поведении == между диктовками, которое четко определено в официальном документе, поэтому должно работать в каждой версии Python .Перейти к поиску:

    • "Словари сравнивают равные, если и только если они имеют одинаковые пары (ключ, значение)."последнее предложение в этой странице
    • "Отображения (экземпляры dict) сравниваются равными тогда и только тогда, когда они имеют равные пары (ключ, значение). Проводит сравнение равенства ключей и элементоврефлексивность «.на этой странице
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...