Supposons que j'ai deux Python - dictA
et dictB
. J'ai besoin de savoir s'il existe des clés présentes dans dictB
. mais pas dans dictA
. Quel est le moyen le plus rapide de s'y prendre?
Devrais-je convertir les clés du dictionnaire en un jeu, puis continuer?
Intéressé de connaître vos pensées ...
Merci pour vos réponses.
Toutes mes excuses pour ne pas avoir posé ma question correctement. Mon scénario est le suivant: j'ai un dictA
qui peut être identique à dictB
ou il peut manquer certaines clés par rapport à dictB
ou la valeur de certaines clés peut être différent, qui doit être défini sur celui de la valeur de dictA
key.
Le problème est que le dictionnaire n’a pas de standard et peut avoir des valeurs qui peuvent être dictées.
Dire
dictA={'key1':a, 'key2':b, 'key3':{'key11':cc, 'key12':dd}, 'key4':{'key111':{....}}}
dictB={'key1':a, 'key2:':newb, 'key3':{'key11':cc, 'key12':newdd, 'key13':ee}.......
Donc, la valeur 'key2' doit être réinitialisée à la nouvelle valeur et 'key13' doit être ajouté à l'intérieur du dict. La valeur de la clé n'a pas de format fixe. Ce peut être une simple valeur ou un dict ou un dict of dict.
Vous pouvez utiliser les opérations définies sur les touches:
diff = set(dictb.keys()) - set(dicta.keys())
Voici une classe pour trouver toutes les possibilités: ce qui a été ajouté, ce qui a été supprimé, quelles paires clé-valeur sont identiques et quelles paires clé-valeur sont modifiées.
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])
Voici un exemple de sortie:
>>> 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'])
Disponible en tant que dépôt github: https://github.com/hughdbrown/dictdiffer
Au cas où vous voudriez la différence de manière récursive, j’ai écrit un paquet pour Python: https://github.com/seperman/deepdiff
Installer depuis PyPi:
pip install deepdiff
Importer
>>> from deepdiff import DeepDiff
>>> from pprint import pprint
>>> from __future__ import print_function # In case running on Python 2
Le même objet retourne vide
>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = t1
>>> print(DeepDiff(t1, t2))
{}
Le type d'un article a changé
>>> 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}}}
La valeur d'un article a changé
>>> 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}}}
Article ajouté et/ou supprimé
>>> 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}}}
Différence de chaîne
>>> 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'}}}
Différence de chaîne 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
Changement de type
>>> 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]}}}
Différence de liste
>>> 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}}
Différence de liste 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}}}
Lister la différence en ignorant l'ordre ou les doublons: (avec les mêmes dictionnaires que ci-dessus)
>>> 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)
{}
Liste contenant le dictionnaire:
>>> 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}}}
Ensembles:
>>> 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]']}
Tuples nommés:
>>> 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}}}
Objets personnalisés:
>>> 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}}}
Attribut d'objet ajouté:
>>> t2.c = "new attribute"
>>> pprint(DeepDiff(t1, t2))
{'attribute_added': ['root.c'],
'values_changed': {'root.b': {'newvalue': 2, 'oldvalue': 1}}}
pas sûr que ce soit "rapide" ou non, mais normalement, on peut le faire
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
Comme Alex Martelli l'a écrit, si vous voulez simplement vérifier si une clé de B n'est pas dans A, any(True for k in dictB if k not in dictA)
serait le chemin à parcourir.
Pour trouver les clés manquantes:
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
Ces deux solutions ont donc à peu près la même vitesse.
Si vous voulez vraiment dire exactement ce que vous dites (que vous avez seulement besoin de savoir SI "il y a des clés" dans B et non dans A, pas QUELLES pourraient être celles-ci), le moyen le plus rapide devrait être:
if any(True for k in dictB if k not in dictA): ...
Si vous avez réellement besoin de savoir quelles clés, le cas échéant, sont en B et non en A, et pas seulement "SI", il existe des clés, alors les réponses existantes sont tout à fait appropriées (mais je suggère plus de précision dans les questions futures bien ce que vous voulez dire ;-).
set(dictA.keys()).intersection(dictB.keys())
La réponse principale de hughdbrown suggère d'utiliser la différence d'ensemble, ce qui est certainement la meilleure approche:
diff = set(dictb.keys()) - set(dicta.keys())
Le problème avec ce code est qu'il construit deux listes uniquement pour créer deux ensembles, ce qui fait perdre 4N temps et 2N espace. C'est aussi un peu plus compliqué que nécessaire.
Habituellement, ce n'est pas grave, mais si c'est le cas:
diff = dictb.keys() - dicta
collections.abc.Mapping
A un KeysView
qui agit comme un Set
=.Dans Python 2, keys()
renvoie une liste des clés, pas un KeysView
. Vous devez donc demander directement viewkeys()
.
diff = dictb.viewkeys() - dicta
Pour le code double version 2.7/3.x, vous utilisez, espérons-le, six
ou quelque chose de similaire, de sorte que vous puissiez utiliser six.viewkeys(dictb)
:
diff = six.viewkeys(dictb) - dicta
Dans 2.4-2.6, il n'y a pas de KeysView
. Mais vous pouvez au moins réduire le coût de 4N à N en construisant votre ensemble gauche directement à partir d'un itérateur, au lieu de créer d'abord une liste:
diff = set(dictb) - dicta
J'ai un dictA qui peut être le même que dictB ou peut avoir certaines clés manquantes par rapport à dictB ou bien la valeur de certaines clés peut être différente
Donc, vous n'avez vraiment pas besoin de comparer les clés, mais les éléments. Un ItemsView
n'est qu'un Set
si les valeurs sont hashable, comme des chaînes. S'ils le sont, c'est facile:
diff = dictb.items() - dicta.items()
Bien que la question ne demande pas directement un diff récursif, certaines des valeurs exemples sont des dict, et il semble que la sortie attendue les diffère de manière récursive. Il y a déjà plusieurs réponses ici montrant comment faire cela.
Il y a un autre question dans stackoverflow à propos de cet argument et je dois admettre qu'il existe une solution simple expliquée: le bibliothèque datadiff de python = aide à imprimer la différence entre deux dictionnaires.
Ceci est une vieille question et demande un peu moins que ce dont j'avais besoin afin que cette réponse résout en réalité plus que cette question demande. Les réponses à cette question m'ont aidé à résoudre le problème suivant:
Tout cela, combiné à JSON, fournit un support de stockage de configuration assez puissant.
La solution ( également sur 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
Voici un moyen qui fonctionnera, permettant aux clés dont le résultat est évalué à False
, tout en utilisant une expression génératrice de tomber plus tôt si possible. Ce n'est pas exceptionnellement jolie cependant.
any(map(lambda x: True, (k for k in b if k not in a)))
EDIT:
THC4k a posté une réponse à mon commentaire sur une autre réponse. Voici une façon plus jolie et plus agréable de faire ce qui précède:
any(True for k in b if k not in a)
Je ne sais pas comment cela ne m'a jamais traversé l'esprit ...
Si activé 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() )
qu'en est-il de standart (comparer l'objet complet)
PyDev-> nouveau module PyDev-> Module: 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 __== "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()
Ma recette de différence symétrique entre deux dictionnaires:
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)
Et le résultat est:
param dict1 dict2
1 a b
2 b a
5 e N\A
6 N\A f
@ Maxx a une excellente réponse, utilisez les outils unittest
fournis par 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)
Ensuite, n'importe où dans votre code, vous pouvez appeler:
try:
Test().testDict(dict1, dict2)
except Exception, e:
print e
Le résultat obtenu ressemble à celui de diff
, en imprimant joliment les dictionnaires avec +
ou -
précédant chaque ligne différente.
voici une solution qui permet de comparer plus de deux 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
exemple d'utilisation:
import matplotlib.pyplot as plt
diff_dict([plt.rcParams, plt.rcParamsDefault, plt.matplotlib.rcParamsOrig])
Comme mentionné dans d'autres réponses, unittest produit une sortie de Nice pour comparer les dessins, mais dans cet exemple, nous ne voulons pas avoir à construire d'abord un test complet.
En éliminant la source la plus faible, il semble que vous puissiez obtenir une solution juste avec juste ceci:
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())
)
alors
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)
Résulte en:
{0: 112,
- 1: 121,
- 2: 116,
+ 1: 'spam',
+ 2: [1, 2, 3],
3: 104,
- 4: 111,
? ^
+ 4: 111}
? ^
- 5: 110}
Où:
Comme dans unittest, le seul inconvénient est que le mappage final peut être considéré comme un diff, en raison de la virgule/crochet suivant.
Voici une solution pour comparer en profondeur 2 clés de dictionnaires:
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
Essayez ceci pour trouver l'intersection, les clés qui se trouvent dans les deux dictionnaires, si vous voulez les clés non trouvées dans le deuxième dictionnaire, utilisez simplement le not in ...
intersect = filter(lambda x, dictB=dictB.keys(): x in dictB, dictA.keys())
Basé sur la réponse de ghostdog74,
dicta = {"a":1,"d":2}
dictb = {"a":5,"d":2}
for value in dicta.values():
if not value in dictb.values():
print value
imprimera différer la valeur de dicta
Si vous souhaitez une solution intégrée permettant une comparaison complète avec des structures dict arbitraires, la réponse de @ Maxx est un bon début.
import unittest
test = unittest.TestCase()
test.assertEqual(dictA, dictB)