web-dev-qa-db-de.com

Python: Prüfen Sie, ob ein Wörterbuch eine Teilmenge eines anderen größeren Wörterbuchs ist

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.

70
Jamey

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)
50
augurar

Hinweis für Benutzer, die dies für Unit-Tests benötigen: Es gibt auch eine assertDictContainsSubset()-Methode in Pythons TestCase-Klasse.

http://docs.python.org/2/library/unittest.html?highlight=assertdictcontainssubset#unittest.TestCase.assertDictContainsSubset

Es ist jedoch in 3.2 veraltet, nicht sicher warum, vielleicht gibt es einen Ersatz dafür.

31
gitaarik

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))

20
kashchey

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).

14
blubberdiblub
>>> 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
>>>
10
robert king

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. ;)

4
Alois Mahdal

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.

  1. "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.

  2. 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.

  3. 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:

    • Msgstr "Wörterbücher sind genau dann gleich, wenn sie die gleichen Paare (Schlüssel, Wert) haben." ist der letzte Satz in diese Seite
    • "Zuordnungen (Instanzen von dict) vergleichen genau und nur dann Gleichheit, wenn sie gleiche Paare (Schlüssel, Wert) haben. Der Gleichheitsvergleich der Schlüssel und Elemente erzwingt die Reflexivität." in diese Seite
3
RayLuo

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

2
BPL

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.

0

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
0
NutCracker

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






0
Zaro

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
0

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
0
timthelion