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 ответов:
преобразовать в пары элементов и проверить на сдерживание.
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
класса.однако он устарел в 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% надежное решение, поэтому я задокументировал то, что я нашел в этом ответе.
"весть-союзник" говорит,
small_dict <= big_dict
было бы самым интуитивным способом, но слишком плохо, что это не работает.{'a': 1} < {'a': 1, 'b': 2}
по-видимому, работает в Python 2, но это не является надежным, потому что официальный документ явно вызывает его. Перейти к поиску " результаты, отличные от равенства, разрешены последовательно, но не определены."в в этом разделе. Не говоря уже о том, что сравнение 2 dicts в Python 3 приводит к исключению TypeError.вторая наиболее интуитивная вещь
small.viewitems() <= big.viewitems()
только для Python 2.7, иsmall.items() <= big.items()
для Python 3. Но есть один нюанс: это потенциально багги. Если ваша программа потенциально может быть использована на Python d1.items() <= d2.items() на самом деле сравнивать 2 списка кортежей, без определенного порядка, так конечный результат будет ненадежным, и это станет неприятной ошибкой в вашей программе. Я не стремлюсь писать еще одну реализацию для PythonЯ остаюсь с @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