Ich versuche, eine benutzerdefinierte Filtermethode zu schreiben, die eine beliebige Anzahl von kwargs verwendet und eine Liste mit den Elementen einer datenbankähnlichen Liste zurückgibt, die diese kwargs enthält.
Nehmen Sie beispielsweise an, dass d1 = {'a':'2', 'b':'3'}
und d2
= dasselbe sind. d1 == d2
ergibt True. Angenommen, d2
= dasselbe plus ein paar andere Dinge. Meine Methode muss in der Lage sein zu erkennen, ob d1 in d2 ist, aber Python kann dies nicht mit Wörterbüchern.
Kontext:
Ich habe eine Word-Klasse und jedes Objekt hat Eigenschaften wie Word
, definition
, part_of_speech
und so weiter. Ich möchte eine Filtermethode in der Hauptliste dieser Wörter aufrufen können, z. B. Word.objects.filter(Word='jump', part_of_speech='verb-intransitive')
. Ich kann nicht herausfinden, wie man diese Schlüssel und Werte gleichzeitig verwaltet. Dies könnte jedoch eine größere Funktionalität außerhalb dieses Kontextes für andere Personen haben.
Konvertieren Sie in Elementpaare und prüfen Sie auf Eindämmung.
all(item in superset.items() for item in subset.items())
Die Optimierung bleibt dem Leser als Übung überlassen.
In Python 3 können Sie dict.items()
verwenden, um eine satzähnliche Ansicht der Dict-Elemente zu erhalten. Sie können dann mit dem Operator <=
testen, ob eine Ansicht eine "Teilmenge" der anderen ist:
d1.items() <= d2.items()
Verwenden Sie in Python 2.7 dict.viewitems()
, um dasselbe zu tun:
d1.viewitems() <= d2.viewitems()
In Python 2.6 und darunter benötigen Sie eine andere Lösung, z. B. all()
:
all(key in d2 and d2[key] == d1[key] for key in d1)
Hinweis für Benutzer, die dies für Unit-Tests benötigen: Es gibt auch eine assertDictContainsSubset()
-Methode in Pythons TestCase
-Klasse.
Es ist jedoch in 3.2 veraltet, nicht sicher warum, vielleicht gibt es einen Ersatz dafür.
verwenden Sie für Schlüssel und Werte Folgendes: set(d1.items()).issubset(set(d2.items()))
wenn Sie nur Schlüssel überprüfen müssen: set(d1).issubset(set(d2))
Der Vollständigkeit halber können Sie auch Folgendes tun:
def is_subdict(small, big):
return dict(big, **small) == big
Ich mache jedoch keinerlei Ansprüche bezüglich Geschwindigkeit (oder deren Fehlen) oder Lesbarkeit (oder deren Fehlen).
>>> 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
kontext:
>>> 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
>>>
Meine Funktion für den gleichen Zweck, dies rekursiv:
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
In Ihrem Beispiel sollte dictMatch(d1, d2)
True zurückgeben, auch wenn d2 andere Elemente enthält. Außerdem gilt dies auch für niedrigere Ebenen:
d1 = {'a':'2', 'b':{3: 'iii'}}
d2 = {'a':'2', 'b':{3: 'iii', 4: 'iv'},'c':'4'}
dictMatch(d1, d2) # True
Hinweise: Es könnte eine noch bessere Lösung geben, die die if type(pvalue) is dict
-Klausel vermeidet und auf eine noch größere Bandbreite von Fällen (wie Hashlisten usw.) anwendbar ist. Auch die Rekursion ist hier nicht eingeschränkt, also auf eigenes Risiko. ;)
Diese scheinbar einfache Angelegenheit kostet mich ein paar Stunden Recherche, um eine 100% zuverlässige Lösung zu finden. Ich habe also dokumentiert, was ich in dieser Antwort gefunden habe.
"Pythonic-ally" wäre small_dict <= big_dict
der intuitivste Weg, aber schade, dass es funktioniert nicht. {'a': 1} < {'a': 1, 'b': 2}
funktioniert anscheinend in Python 2, ist aber nicht zuverlässig, da es in der offiziellen Dokumentation explizit angezeigt wird. Suche starten "Andere Ergebnisse als Gleichheit werden konsistent gelöst, aber nicht anders definiert." in dieser Abschnitt . Der Vergleich von 2 Diktaten in Python 3 führt zu einer TypeError-Ausnahme.
Die zweit intuitivste Sache ist small.viewitems() <= big.viewitems()
nur für Python 2.7 und small.items() <= big.items()
für Python 3. Aber es gibt eine Einschränkung: es ist möglicherweise fehlerhaft. Wenn Ihr Programm möglicherweise auf Python <= 2.6 verwendet werden kann, vergleicht d1.items() <= d2.items()
tatsächlich zwei Listen von Tupeln ohne bestimmte Reihenfolge. Das Endergebnis ist also unzuverlässig und wird zu einem bösen Fehler in Ihrem Programm. Ich möchte nicht noch eine weitere Implementierung für Python <= 2.6 schreiben, aber ich fühle mich immer noch nicht sicher, dass mein Code einen bekannten Fehler enthält (selbst wenn er auf einer nicht unterstützten Plattform ist). Also verzichte ich auf diesen Ansatz.
Ich beruhige mich mit der Antwort von @blubberdiblub (Kredit geht an ihn):
def is_subdict(small, big):
return dict(big, **small) == big
Es sei darauf hingewiesen, dass diese Antwort auf dem ==
-Verhalten zwischen Diktaten beruht, das im offiziellen Dokument eindeutig definiert ist, und daher sollte in jeder Python-Version funktionieren. Suche gehen:
Hier ist eine allgemeine rekursive Lösung für das angegebene Problem:
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()
HINWEIS: Der Originalcode schlägt in bestimmten Fällen fehl, die Credits für das Fixing gehen an @ olivier-melançon
Eine kurze rekursive Implementierung, die für verschachtelte Wörterbücher funktioniert:
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
Dies wird die a und b Dikte verbrauchen. Wenn jemand einen guten Weg kennt, dies zu vermeiden, ohne auf teilweise iterative Lösungen wie in anderen Antworten zurückzugreifen, teilen Sie mir dies bitte mit. Ich würde eine Möglichkeit brauchen, ein Diktat basierend auf einem Schlüssel in Kopf und Schwanz aufzuteilen.
Dieser Code ist als Programmierübung nützlicher und wahrscheinlich viel langsamer als andere Lösungen, die Rekursion und Iteration mischen. @Nussknacker's Lösung ist ziemlich gut für verschachtelte Wörterbücher.
Ich weiß, dass diese Frage alt ist, aber hier ist meine Lösung zur Überprüfung, ob ein verschachteltes Wörterbuch Teil eines anderen verschachtelten Wörterbuchs ist. Die Lösung ist rekursiv.
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
Wenn es Ihnen nichts ausmacht, pydash zu benutzen, gibt es dort is_match, das genau das tut:
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
Hier ist eine Lösung, die auch richtig in Listen und Mengen im Wörterbuch wiederkehrt. Sie können dies auch für Listen mit Dikten usw. verwenden.
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
Diese Funktion funktioniert für nicht hashierbare Werte. Ich denke auch, dass es klar und gut lesbar ist.
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