Вычислить разницу в ключах, содержащихся в двух словарях Python
Предположим, что у меня есть два словаря Python - dictA
и dictB
. Мне нужно выяснить, есть ли какие-либо ключи, которые присутствуют в dictB
, но не в dictA
. Каков самый быстрый способ сделать это?
Должен ли я конвертировать словарные ключи в набор, а затем идти?
Интересует ваши мысли...
Спасибо за ваши ответы.
Извиняюсь за то, что я не задал свой вопрос должным образом.
Мой сценарий выглядит следующим образом: у меня есть dictA
, который может быть таким же, как dictB
, или может отсутствовать несколько ключей по сравнению с dictB
, иначе значение некоторых ключей может отличаться, что должно быть установлено на значения dictA
.
Проблема заключается в том, что словарь не имеет стандарта и может иметь значения, которые могут быть dict dict.
Скажем
dictA={'key1':a, 'key2':b, 'key3':{'key11':cc, 'key12':dd}, 'key4':{'key111':{....}}}
dictB={'key1':a, 'key2:':newb, 'key3':{'key11':cc, 'key12':newdd, 'key13':ee}.......
Значит, значение "key2" должно быть reset к новому значению, а "ключ13" должен быть добавлен внутри dict.
Значение ключа не имеет фиксированного формата. Это может быть простое значение или dict или dict dict.
Ответы
Ответ 1
Вы можете использовать операции набора по клавишам:
diff = set(dictb.keys()) - set(dicta.keys())
Вот класс, чтобы найти все возможности: что было добавлено, что было удалено, какие пары ключ-значение одинаковы и какие пары ключ-значение изменены.
class DictDiffer(object):
"""
Calculate the difference between two dictionaries as:
(1) items added
(2) items removed
(3) keys same in both but changed values
(4) keys same in both and unchanged values
"""
def __init__(self, current_dict, past_dict):
self.current_dict, self.past_dict = current_dict, past_dict
self.set_current, self.set_past = set(current_dict.keys()), set(past_dict.keys())
self.intersect = self.set_current.intersection(self.set_past)
def added(self):
return self.set_current - self.intersect
def removed(self):
return self.set_past - self.intersect
def changed(self):
return set(o for o in self.intersect if self.past_dict[o] != self.current_dict[o])
def unchanged(self):
return set(o for o in self.intersect if self.past_dict[o] == self.current_dict[o])
Вот пример вывода:
>>> a = {'a': 1, 'b': 1, 'c': 0}
>>> b = {'a': 1, 'b': 2, 'd': 0}
>>> d = DictDiffer(b, a)
>>> print "Added:", d.added()
Added: set(['d'])
>>> print "Removed:", d.removed()
Removed: set(['c'])
>>> print "Changed:", d.changed()
Changed: set(['b'])
>>> print "Unchanged:", d.unchanged()
Unchanged: set(['a'])
Доступно как репозиторий github:
https://github.com/hughdbrown/dictdiffer
Ответ 2
Если вы хотите, чтобы рекурсия была рекурсивно, я написал пакет для python:
https://github.com/seperman/deepdiff
Установка
Установить из PyPi:
pip install deepdiff
Пример использования
Импорт
>>> from deepdiff import DeepDiff
>>> from pprint import pprint
>>> from __future__ import print_function # In case running on Python 2
Тот же объект возвращает пустой
>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = t1
>>> print(DeepDiff(t1, t2))
{}
Изменен тип элемента
>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:"2", 3:3}
>>> pprint(DeepDiff(t1, t2), indent=2)
{ 'type_changes': { 'root[2]': { 'newtype': <class 'str'>,
'newvalue': '2',
'oldtype': <class 'int'>,
'oldvalue': 2}}}
Значение элемента изменилось
>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:4, 3:3}
>>> pprint(DeepDiff(t1, t2), indent=2)
{'values_changed': {'root[2]': {'newvalue': 4, 'oldvalue': 2}}}
Элемент добавлен и/или удален
>>> t1 = {1:1, 2:2, 3:3, 4:4}
>>> t2 = {1:1, 2:4, 3:3, 5:5, 6:6}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff)
{'dic_item_added': ['root[5]', 'root[6]'],
'dic_item_removed': ['root[4]'],
'values_changed': {'root[2]': {'newvalue': 4, 'oldvalue': 2}}}
Разница строк
>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world"}}
>>> t2 = {1:1, 2:4, 3:3, 4:{"a":"hello", "b":"world!"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{ 'values_changed': { 'root[2]': {'newvalue': 4, 'oldvalue': 2},
"root[4]['b']": { 'newvalue': 'world!',
'oldvalue': 'world'}}}
Разница строк 2
>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world!\nGoodbye!\n1\n2\nEnd"}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world\n1\n2\nEnd"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{ 'values_changed': { "root[4]['b']": { 'diff': '--- \n'
'+++ \n'
'@@ -1,5 +1,4 @@\n'
'-world!\n'
'-Goodbye!\n'
'+world\n'
' 1\n'
' 2\n'
' End',
'newvalue': 'world\n1\n2\nEnd',
'oldvalue': 'world!\n'
'Goodbye!\n'
'1\n'
'2\n'
'End'}}}
>>>
>>> print (ddiff['values_changed']["root[4]['b']"]["diff"])
---
+++
@@ -1,5 +1,4 @@
-world!
-Goodbye!
+world
1
2
End
Изменение типа
>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world\n\n\nEnd"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{ 'type_changes': { "root[4]['b']": { 'newtype': <class 'str'>,
'newvalue': 'world\n\n\nEnd',
'oldtype': <class 'list'>,
'oldvalue': [1, 2, 3]}}}
Разница в списке
>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3, 4]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{'iterable_item_removed': {"root[4]['b'][2]": 3, "root[4]['b'][3]": 4}}
Перечислить разницу 2:
>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 3, 2, 3]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{ 'iterable_item_added': {"root[4]['b'][3]": 3},
'values_changed': { "root[4]['b'][1]": {'newvalue': 3, 'oldvalue': 2},
"root[4]['b'][2]": {'newvalue': 2, 'oldvalue': 3}}}
Разница в списках, игнорирующая порядок или дубликаты: (с теми же словарями, что и выше)
>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 3, 2, 3]}}
>>> ddiff = DeepDiff(t1, t2, ignore_order=True)
>>> print (ddiff)
{}
Список, содержащий словарь:
>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, {1:1, 2:2}]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, {1:3}]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{ 'dic_item_removed': ["root[4]['b'][2][2]"],
'values_changed': {"root[4]['b'][2][1]": {'newvalue': 3, 'oldvalue': 1}}}
Наборы:
>>> t1 = {1, 2, 8}
>>> t2 = {1, 2, 3, 5}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (DeepDiff(t1, t2))
{'set_item_added': ['root[3]', 'root[5]'], 'set_item_removed': ['root[8]']}
Именованные кортежи:
>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> t1 = Point(x=11, y=22)
>>> t2 = Point(x=11, y=23)
>>> pprint (DeepDiff(t1, t2))
{'values_changed': {'root.y': {'newvalue': 23, 'oldvalue': 22}}}
Пользовательские объекты:
>>> class ClassA(object):
... a = 1
... def __init__(self, b):
... self.b = b
...
>>> t1 = ClassA(1)
>>> t2 = ClassA(2)
>>>
>>> pprint(DeepDiff(t1, t2))
{'values_changed': {'root.b': {'newvalue': 2, 'oldvalue': 1}}}
Добавлен атрибут объекта:
>>> t2.c = "new attribute"
>>> pprint(DeepDiff(t1, t2))
{'attribute_added': ['root.c'],
'values_changed': {'root.b': {'newvalue': 2, 'oldvalue': 1}}}
Ответ 3
не уверен, что это "быстро" или нет, но обычно это можно сделать
dicta = {"a":1,"b":2,"c":3,"d":4}
dictb = {"a":1,"d":2}
for key in dicta.keys():
if not key in dictb:
print key
Ответ 4
Как писал Алекс Мартелли, если вы просто хотите проверить, не является ли какой-либо ключ в B не в A, any(True for k in dictB if k not in dictA)
- это путь.
Чтобы найти отсутствующие ключи:
diff = set(dictB)-set(dictA) #sets
C:\Dokumente und Einstellungen\thc>python -m timeit -s "dictA =
dict(zip(range(1000),range
(1000))); dictB = dict(zip(range(0,2000,2),range(1000)))" "diff=set(dictB)-set(dictA)"
10000 loops, best of 3: 107 usec per loop
diff = [ k for k in dictB if k not in dictA ] #lc
C:\Dokumente und Einstellungen\thc>python -m timeit -s "dictA =
dict(zip(range(1000),range
(1000))); dictB = dict(zip(range(0,2000,2),range(1000)))" "diff=[ k for k in dictB if
k not in dictA ]"
10000 loops, best of 3: 95.9 usec per loop
Таким образом, эти два решения имеют одинаковую скорость.
Ответ 5
Если вы действительно имеете в виду то, что вы говорите (вам нужно всего лишь выяснить, есть ли какие-либо ключи в B, а не в A, а не WHICH ONES, если они есть), самый быстрый способ:
if any(True for k in dictB if k not in dictA): ...
Если вам действительно нужно выяснить WHICH KEYS, если они есть, находятся в B, а не в A, а не только "IF", есть такие ключи, тогда существующие ответы вполне уместны (но я предлагаю более точную оценку в будущем если это действительно то, что вы имеете в виду; -).
Ответ 6
Использовать set()
:
set(dictA.keys()).intersection(dictB.keys())
Ответ 7
Есть другой вопрос fooobar.com/questions/42800/..., и я должен признать, что существует простое объяснение: библиотека datadiff в python помогает распечатать разницу между двумя словарями.
Ответ 8
Здесь способ, который будет работать, позволяет использовать ключи, которые оцениваются до False
, и по-прежнему использует выражение генератора, чтобы оно выпало на ранней стадии, если это возможно. Это не исключительно красиво, хотя.
any(map(lambda x: True, (k for k in b if k not in a)))
EDIT:
THC4k отправил ответ на мой комментарий к другому ответу. Здесь лучший, более красивый способ сделать это:
any(True for k in b if k not in a)
Не знаю, как это никогда не приходило мне в голову...
Ответ 9
Это старый вопрос и требует немного меньше того, что мне нужно, поэтому этот ответ фактически решает больше, чем задает этот вопрос. Ответы в этом вопросе помогли мне решить следующее:
- (задано) Запись различий между двумя словарями
- Объединить отличия от # 1 в базовом словаре
- (задано) Объединить различия между двумя словарями (лечить словарь # 2, как если бы это был словарь diff)
- Попробуйте обнаружить движения элементов, а также изменения.
- (спросил) Все это рекурсивно
Все это в сочетании с JSON обеспечивает довольно мощную поддержку хранилища конфигурации.
Решение (также на github):
from collections import OrderedDict
from pprint import pprint
class izipDestinationMatching(object):
__slots__ = ("attr", "value", "index")
def __init__(self, attr, value, index):
self.attr, self.value, self.index = attr, value, index
def __repr__(self):
return "izip_destination_matching: found match by '%s' = '%s' @ %d" % (self.attr, self.value, self.index)
def izip_destination(a, b, attrs, addMarker=True):
"""
Returns zipped lists, but final size is equal to b with (if shorter) a padded with nulls
Additionally also tries to find item reallocations by searching child dicts (if they are dicts) for attribute, listed in attrs)
When addMarker == False (patching), final size will be the longer of a, b
"""
for idx, item in enumerate(b):
try:
attr = next((x for x in attrs if x in item), None) # See if the item has any of the ID attributes
match, matchIdx = next(((orgItm, idx) for idx, orgItm in enumerate(a) if attr in orgItm and orgItm[attr] == item[attr]), (None, None)) if attr else (None, None)
if match and matchIdx != idx and addMarker: item[izipDestinationMatching] = izipDestinationMatching(attr, item[attr], matchIdx)
except:
match = None
yield (match if match else a[idx] if len(a) > idx else None), item
if not addMarker and len(a) > len(b):
for item in a[len(b) - len(a):]:
yield item, item
def dictdiff(a, b, searchAttrs=[]):
"""
returns a dictionary which represents difference from a to b
the return dict is as short as possible:
equal items are removed
added / changed items are listed
removed items are listed with value=None
Also processes list values where the resulting list size will match that of b.
It can also search said list items (that are dicts) for identity values to detect changed positions.
In case such identity value is found, it is kept so that it can be re-found during the merge phase
@param a: original dict
@param b: new dict
@param searchAttrs: list of strings (keys to search for in sub-dicts)
@return: dict / list / whatever input is
"""
if not (isinstance(a, dict) and isinstance(b, dict)):
if isinstance(a, list) and isinstance(b, list):
return [dictdiff(v1, v2, searchAttrs) for v1, v2 in izip_destination(a, b, searchAttrs)]
return b
res = OrderedDict()
if izipDestinationMatching in b:
keepKey = b[izipDestinationMatching].attr
del b[izipDestinationMatching]
else:
keepKey = izipDestinationMatching
for key in sorted(set(a.keys() + b.keys())):
v1 = a.get(key, None)
v2 = b.get(key, None)
if keepKey == key or v1 != v2: res[key] = dictdiff(v1, v2, searchAttrs)
if len(res) <= 1: res = dict(res) # This is only here for pretty print (OrderedDict doesn't pprint nicely)
return res
def dictmerge(a, b, searchAttrs=[]):
"""
Returns a dictionary which merges differences recorded in b to base dictionary a
Also processes list values where the resulting list size will match that of a
It can also search said list items (that are dicts) for identity values to detect changed positions
@param a: original dict
@param b: diff dict to patch into a
@param searchAttrs: list of strings (keys to search for in sub-dicts)
@return: dict / list / whatever input is
"""
if not (isinstance(a, dict) and isinstance(b, dict)):
if isinstance(a, list) and isinstance(b, list):
return [dictmerge(v1, v2, searchAttrs) for v1, v2 in izip_destination(a, b, searchAttrs, False)]
return b
res = OrderedDict()
for key in sorted(set(a.keys() + b.keys())):
v1 = a.get(key, None)
v2 = b.get(key, None)
#print "processing", key, v1, v2, key not in b, dictmerge(v1, v2)
if v2 is not None: res[key] = dictmerge(v1, v2, searchAttrs)
elif key not in b: res[key] = v1
if len(res) <= 1: res = dict(res) # This is only here for pretty print (OrderedDict doesn't pprint nicely)
return res
Ответ 10
Если на Python ≥ 2.7:
# update different values in dictB
# I would assume only dictA should be updated,
# but the question specifies otherwise
for k in dictA.viewkeys() & dictB.viewkeys():
if dictA[k] != dictB[k]:
dictB[k]= dictA[k]
# add missing keys to dictA
dictA.update( (k,dictB[k]) for k in dictB.viewkeys() - dictA.viewkeys() )
Ответ 11
как насчет стандартного (сравните FULL Object)
PyDev- > новый модуль PyDev- > Модуль: unittest
import unittest
class Test(unittest.TestCase):
def testName(self):
obj1 = {1:1, 2:2}
obj2 = {1:1, 2:2}
self.maxDiff = None # sometimes is usefull
self.assertDictEqual(d1, d2)
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()
Ответ 12
Вот решение для глубокого сравнения двух словарей:
def compareDictKeys(dict1, dict2):
if type(dict1) != dict or type(dict2) != dict:
return False
keys1, keys2 = dict1.keys(), dict2.keys()
diff = set(keys1) - set(keys2) or set(keys2) - set(keys1)
if not diff:
for key in keys1:
if (type(dict1[key]) == dict or type(dict2[key]) == dict) and not compareDictKeys(dict1[key], dict2[key]):
diff = True
break
return not diff
Ответ 13
здесь решение, которое может сравнивать более двух dicts:
def diff_dict(dicts, default=None):
diff_dict = {}
# add 'list()' around 'd.keys()' for python 3 compatibility
for k in set(sum([d.keys() for d in dicts], [])):
# we can just use "values = [d.get(k, default) ..." below if
# we don't care that d1[k]=default and d2[k]=missing will
# be treated as equal
if any(k not in d for d in dicts):
diff_dict[k] = [d.get(k, default) for d in dicts]
else:
values = [d[k] for d in dicts]
if any(v != values[0] for v in values):
diff_dict[k] = values
return diff_dict
Пример использования :
import matplotlib.pyplot as plt
diff_dict([plt.rcParams, plt.rcParamsDefault, plt.matplotlib.rcParamsOrig])
Ответ 14
Не уверен, что это все еще актуально, но я столкнулся с этой проблемой, в моей ситуации мне просто нужно было вернуть словарь изменений для всех вложенных словарей и т.д. и т.д. Не удалось найти хорошее решение, но я действительно закончил написание простой функции для этого. Надеюсь, это поможет,
Ответ 15
Если вы хотите встроенное решение для полного сравнения с произвольными структурами dict, ответ @Maxx является хорошим началом.
import unittest
test = unittest.TestCase()
test.assertEqual(dictA, dictB)
Ответ 16
На основе ответа ghostdog74,
dicta = {"a":1,"d":2}
dictb = {"a":5,"d":2}
for value in dicta.values():
if not value in dictb.values():
print value
будет печатать различное значение dictap >
Ответ 17
Ниже я создал два словаря. Мне нужно вернуть ключи и значения между ними. Я застрял здесь. Я не уверен, какой путь правильный. Мне нужно знать, как получить ключевую разницу в значениях. Я хочу сначала проверить, совпадают ли они, и если они не печатают разницу в значении ключа. Я не хочу использовать глубокий diff. Я не знаю, чтобы сравнить, если они одинаковы?
num_list = [1,2]
val_list = [0,1]
dict1 = dict(zip(num_list,val_list))
print dict1
num_list2= [1,2]
val_list2 = [0,6]
dict2 = dict(zip(num_list2,val_list2))
print dict2
if dict1 == dict2
вывод: в настоящее время
{1: 0, 2: 1}
{1: 0, 2: 6}
Ответ 18
Мой рецепт симметричной разницы между двумя словарями:
def find_dict_diffs(dict1, dict2):
unequal_keys = []
unequal_keys.extend(set(dict1.keys()).symmetric_difference(set(dict2.keys())))
for k in dict1.keys():
if dict1.get(k, 'N\A') != dict2.get(k, 'N\A'):
unequal_keys.append(k)
if unequal_keys:
print 'param', 'dict1\t', 'dict2'
for k in set(unequal_keys):
print str(k)+'\t'+dict1.get(k, 'N\A')+'\t '+dict2.get(k, 'N\A')
else:
print 'Dicts are equal'
dict1 = {1:'a', 2:'b', 3:'c', 4:'d', 5:'e'}
dict2 = {1:'b', 2:'a', 3:'c', 4:'d', 6:'f'}
find_dict_diffs(dict1, dict2)
И результат:
param dict1 dict2
1 a b
2 b a
5 e N\A
6 N\A f
Ответ 19
Как упоминалось в других ответах, unittest создает хороший результат для сравнения dicts, но в этом примере мы не хотим сначала создавать целый тест.
Скремблируя источник unittest, похоже, вы можете получить справедливое решение только с этим:
import difflib
import pprint
def diff_dicts(a, b):
if a == b:
return ''
return '\n'.join(
difflib.ndiff(pprint.pformat(a, width=30).splitlines(),
pprint.pformat(b, width=30).splitlines())
)
так
dictA = dict(zip(range(7), map(ord, 'python')))
dictB = {0: 112, 1: 'spam', 2: [1,2,3], 3: 104, 4: 111}
print diff_dicts(dictA, dictB)
Результаты в:
{0: 112,
- 1: 121,
- 2: 116,
+ 1: 'spam',
+ 2: [1, 2, 3],
3: 104,
- 4: 111,
? ^
+ 4: 111}
? ^
- 5: 110}
Где:
- '-' указывает ключ/значения в первом, но не втором dict
- '+' указывает ключ/значения во втором, но не первом dict
Как и в unittest, единственное предостережение состоит в том, что окончательное сопоставление можно рассматривать как diff из-за конечной запятой/скобкой.
Ответ 20
Попробуйте найти это пересечение, ключи, которые находятся в обеих словарях, если вы хотите, чтобы ключи не были найдены на второй дикторе, просто используйте не в...
intersect = filter(lambda x, dictB=dictB.keys(): x in dictB, dictA.keys())
Ответ 21
@Maxx имеет отличный ответ, используйте инструменты unittest
, предоставляемые Python:
import unittest
class Test(unittest.TestCase):
def runTest(self):
pass
def testDict(self, d1, d2, maxDiff=None):
self.maxDiff = maxDiff
self.assertDictEqual(d1, d2)
Затем в любом месте вашего кода вы можете позвонить:
try:
Test().testDict(dict1, dict2)
except Exception, e:
print e
Результирующий результат выглядит как результат из diff
, довольно-печатающий словари с +
или -
, добавляя каждую строку, которая отличается.