Ответ 1
Преобразуйте в пара элементов и проверьте на наличие ограничений.
all(item in superset.items() for item in subset.items())
Оптимизация остается в качестве упражнения для читателя.
Я пытаюсь написать собственный метод фильтрации, который принимает произвольное количество kwargs и возвращает список, содержащий элементы списка, подобного базе данных, которые содержат эти kwargs.
Например, предположим, что d1 = {'a':'2', 'b':'3'}
и d2
= то же самое. d1 == d2
приводит к True. Но предположим, что d2
= то же самое, плюс множество других вещей. Мой метод должен быть способен определить d1 в d2, но Python не может это сделать со словарями.
Context:
У меня есть класс Word, и каждый объект имеет такие свойства, как word
, definition
, part_of_speech
и т.д. Я хочу иметь возможность вызвать метод фильтрации в основном списке этих слов, например Word.objects.filter(word='jump', part_of_speech='verb-intransitive')
. Я не могу понять, как управлять этими ключами и значениями одновременно. Но это может иметь большую функциональность вне этого контекста для других людей.
Преобразуйте в пара элементов и проверьте на наличие ограничений.
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, даже если в нем есть другие вещи, плюс он применяется также к более низким уровням:
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
быть даже лучшее решение, которое позволяет избежать того, if type(pvalue) is dict
и применяется к еще более широкому диапазону случаев (например, списки хэшей и т.д.). Также рекурсия здесь не ограничивается, поэтому используйте свой риск. ;)
Этот, казалось бы, простой вопрос стоит мне на пару часов в исследовании, чтобы найти 100% -ное надежное решение, поэтому я задокументировал, что нашел в этом ответе.
"Pythonic-ally" говорит, 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 <= 2.6, ее d1.items() <= d2.items()
фактически сравнивает 2 списка кортежей без особого порядка, поэтому конечный результат будет ненадежным и станет неприятной ошибкой в вашей программе. Я не хочу писать еще одну реализацию для Python <= 2.6, но мне все еще не очень удобно, что мой код содержит известную ошибку (даже если она находится на неподдерживаемой платформе). Поэтому я отказываюсь от такого подхода.
Я соглашаюсь с @blubberdiblub ответом (кредит ему идет):
def is_subdict(small, big):
return dict(big, **small) == big
Стоит отметить, что этот ответ основывается на поведении ==
между dicts, который четко определен в официальном документе, поэтому должен работать в каждой версии Python. Поиск:
Здесь общее рекурсивное решение заданной задачи:
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
Я знаю, что этот вопрос старый, но вот мое решение для проверки того, является ли один вложенный словарь частью другого вложенного словаря. Решение является рекурсивным.
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
Эта функция работает для недопустимых значений. Я также считаю, что это понятно и легко читать.
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
Если вы не возражаете против использования 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 dicts. Если кто-нибудь знает хороший способ избежать этого, не прибегая к частично итеративным решениям, как в других ответах, пожалуйста, сообщите мне. Мне нужен способ разбить диктовку на голову и хвост на основе ключа.
Этот код более полезен как упражнение по программированию и, вероятно, намного медленнее, чем другие решения, которые смешивают рекурсию и итерацию. @Nutcracker solution довольно хорошо подходит для вложенных словарей.
Вот решение, которое также правильно повторяется в списках и наборах, содержащихся в словаре. Вы также можете использовать это для списков, содержащих диктовки и т.д.
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