J'essaie d'écrire une méthode de filtrage personnalisée qui prend un nombre arbitraire de kwargs et renvoie une liste contenant les éléments d'une liste de type base de données contenant ces kwargs.
Par exemple, supposons que d1 = {'a':'2', 'b':'3'}
et d2
= la même chose. d1 == d2
renvoie True. Mais supposons que d2
= la même chose plus un tas d’autres choses. Ma méthode doit pouvoir dire si d1 dans d2 , mais Python ne peut pas le faire avec les dictionnaires.
Le contexte:
J'ai une classe Word et chaque objet a des propriétés telles que Word
, definition
, part_of_speech
et ainsi de suite. Je veux pouvoir appeler une méthode de filtrage dans la liste principale de ces mots, telle que Word.objects.filter(Word='jump', part_of_speech='verb-intransitive')
. Je n'arrive pas à comprendre comment gérer ces clés et ces valeurs en même temps. Mais cela pourrait avoir une fonctionnalité plus large en dehors de ce contexte pour d'autres personnes.
Convertir en paires d'éléments et vérifier le confinement.
all(item in superset.items() for item in subset.items())
L'optimisation est laissée comme un exercice pour le lecteur.
En Python 3, vous pouvez utiliser dict.items()
pour obtenir une vue d'ensemble des éléments dict. Vous pouvez ensuite utiliser l'opérateur <=
pour vérifier si une vue est un "sous-ensemble" de l'autre:
d1.items() <= d2.items()
Dans Python 2.7, utilisez la dict.viewitems()
pour faire la même chose:
d1.viewitems() <= d2.viewitems()
Dans Python 2.6 et les versions ultérieures, vous aurez besoin d’une solution différente, telle que l’utilisation de all()
:
all(key in d2 and d2[key] == d1[key] for key in d1)
Remarque pour les personnes qui en ont besoin pour les tests unitaires: il existe également une méthode assertDictContainsSubset()
dans la classe TestCase
de Python.
Il est toutefois obsolète en 3.2, je ne sais pas pourquoi, il existe peut-être un remplaçant.
pour la vérification des clés et des valeurs, utilisez: set(d1.items()).issubset(set(d2.items()))
si vous ne devez vérifier que les touches: set(d1).issubset(set(d2))
Pour être complet, vous pouvez aussi faire ceci:
def is_subdict(small, big):
return dict(big, **small) == big
Cependant, je ne fais aucune réclamation que ce soit concernant la vitesse (ou son absence) ou la lisibilité (ou son absence).
>>> 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
le contexte:
>>> 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
>>>
Ma fonction dans le même but, le faire de manière récursive:
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
Dans votre exemple, dictMatch(d1, d2)
devrait renvoyer True même si d2 contient d'autres éléments, il s'applique également aux niveaux inférieurs:
d1 = {'a':'2', 'b':{3: 'iii'}}
d2 = {'a':'2', 'b':{3: 'iii', 4: 'iv'},'c':'4'}
dictMatch(d1, d2) # True
Notes: Il pourrait y avoir une solution encore meilleure qui évite la clause if type(pvalue) is dict
et s’applique à un éventail de cas encore plus large (comme des listes de hachages, etc.). De plus, la récursivité n'est pas limitée ici, utilisez-la à vos risques et périls. ;)
Ce problème apparemment simple me coûte quelques heures en recherche pour trouver une solution fiable à 100%. J'ai donc documenté ce que j'ai trouvé dans cette réponse.
"En pythonique", small_dict <= big_dict
serait le moyen le plus intuitif, mais dommage qu'il soit ne fonctionnera pas. {'a': 1} < {'a': 1, 'b': 2}
fonctionne apparemment en Python 2, mais il n’est pas fiable car la documentation officielle l’appelle explicitement. Lancer la recherche "Les résultats autres que l'égalité sont résolus de manière cohérente, mais ne sont pas définis autrement." dans cette section . Sans parler de la comparaison de 2 dict dans Python 3, on obtient une exception TypeError.
La deuxième chose la plus intuitive est small.viewitems() <= big.viewitems()
pour Python 2.7 uniquement et small.items() <= big.items()
pour Python 3. Mais il y a une mise en garde: il s'agit de potentiellement bogué. Si votre programme peut potentiellement être utilisé sur Python <= 2.6, sa fonction d1.items() <= d2.items()
compare en réalité 2 listes de n-uplets, sans ordre particulier, de sorte que le résultat final ne sera pas fiable et deviendra un bogue méchant dans votre programme. Je ne souhaite pas écrire encore une autre implémentation pour Python <= 2.6, mais je ne suis toujours pas sûr que mon code comporte un bogue connu (même s'il se trouve sur une plate-forme non prise en charge). Alors j'abandonne cette approche.
Je m'installe avec la réponse de @blubberdiblub (le crédit lui revient):
def is_subdict(small, big):
return dict(big, **small) == big
Il convient de noter que cette réponse repose sur le comportement ==
entre les dict, qui est clairement défini dans le document officiel, d'où devrait fonctionner dans toutes les versions de Python. Aller chercher:
Voici une solution générale récursive au problème donné:
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 __== "__main__":
unittest.main()
NOTE: Le code original échouerait dans certains cas, les crédits pour la réparation va à @ olivier-melançon
Une courte implémentation récursive qui fonctionne pour les dictionnaires imbriqués:
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
Cela consommera les dits a et b. Si quelqu'un connaît un bon moyen d'éviter cela sans recourir à des solutions partiellement itératives comme dans d'autres réponses, dites-le-moi. J'aurais besoin d'un moyen de scinder un dict en tête et en queue en fonction d'une clé.
Ce code est plus utile comme exercice de programmation et est probablement beaucoup plus lent que d’autres solutions ici qui combinent récursivité et itération. La solution @ Nutcracker est très utile pour les dictionnaires imbriqués.
Je sais que cette question est ancienne, mais voici ma solution pour vérifier si un dictionnaire imbriqué fait partie d'un autre dictionnaire imbriqué. La solution est récursive.
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
Si cela ne vous dérange pas d'utiliser pydash, il y a is_match qui fait exactement cela:
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
Voici une solution qui revient aussi correctement dans les listes et les ensembles contenus dans le dictionnaire. Vous pouvez également l'utiliser pour des listes contenant des dictées, etc.
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
Cette fonction fonctionne pour les valeurs non-hashable. Je pense aussi que c'est clair et facile à lire.
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