web-dev-qa-db-fra.com

Comment affirmer qu'un dict contient un autre dict sans assertDictContainsSubset en python?

Je sais que assertDictContainsSubset peut le faire dans Python 2.7, mais pour une raison quelconque, il est déconseillé dans Python 3.2. Alors, y a-t-il moyen d'affirmer qu'un dict en contient un autre sans assertDictContainsSubset?

Cela ne semble pas bon:

for item in dic2:
    self.assertIn(item, dic)

un autre bon moyen? Merci

33
JerryCai
>>> d1 = dict(a=1, b=2, c=3, d=4)
>>> d2 = dict(a=1, b=2)
>>> set(d2.items()).issubset( set(d1.items()) )
True

Et l'inverse:

>>> set(d1.items()).issubset( set(d2.items()) )
False

Limitation: les valeurs du dictionnaire doivent être hashable.

22
John1024

Bien que j'utilise pytest, j'ai trouvé l'idée suivante dans un commentaire . Cela fonctionnait vraiment bien pour moi, alors j'ai pensé que cela pourrait être utile ici:

assert dict1.items() <= dict2.items()

pour Python 3 et

assert dict1.viewitems() <= dict2.viewitems()

pour Python 2.

Cela fonctionne avec les éléments non-hashable, mais vous ne pouvez pas savoir exactement quel article échoue.

10
kepler

La solution de John1024 a fonctionné pour moi. Cependant, en cas d’échec, il vous indique uniquement False au lieu de vous indiquer les clés qui ne correspondent pas. J'ai donc essayé d'éviter la méthode d'assertion déconseillée en utilisant d'autres méthodes d'assertion qui produiraient des messages d'échec utiles:

    expected = {}
    for key in input_dict.keys():
        self.assertIn(key, set(response.data.keys()))
        expected[key] = response.data[key]
    self.assertDictEqual(input_dict, expected)
6
Risadinha

Le gros problème avec la réponse acceptée est que cela ne fonctionne pas si vous avez des valeurs non hashable dans les valeurs de vos objets. La deuxième chose est que vous n'obtenez aucune sortie utile - le test réussit ou échoue mais ne vous dit pas quel champ de l'objet est différent.

En tant que tel, il est plus facile de créer simplement un dictionnaire de sous-ensemble, puis de le tester. De cette façon, vous pouvez utiliser la méthode TestCase.assertDictEquals() qui vous donnera une sortie formatée très utile dans votre programme d'exécution de test montrant la différence entre le réel et le prévu.

Je pense que la façon la plus agréable et pythonique de faire cela est avec une compréhension simple du dictionnaire en tant que telle:

from unittest import TestCase


actual = {}
expected = {}

subset = {k:v for k, v in actual.items() if k in expected}
TestCase().assertDictEqual(subset, expected)

NOTE évidemment si vous exécutez votre test dans une méthode qui appartient à une classe enfant qui hérite de TestCase (comme vous devriez le faire presque certainement), alors c'est simplement self.assertDictEqual(subset, expected)

6
Sam Redway

Cela répond à une question un peu plus large que celle que vous posez, mais je l’utilise dans mes harnais de test pour voir si le dictionnaire container contient quelque chose qui ressemble au dictionnaire contained. Ceci vérifie les clés et les valeurs. De plus, vous pouvez utiliser le mot-clé 'ANYTHING' pour indiquer que vous ne vous souciez pas de la correspondance.

def contains(container, contained):
    '''ensure that `contained` is present somewhere in `container`

    EXAMPLES:

    contains(
        {'a': 3, 'b': 4},
        {'a': 3}
    ) # True

    contains(
        {'a': [3, 4, 5]},
        {'a': 3},
    ) # True

    contains(
        {'a': 4, 'b': {'a':3}},
        {'a': 3}
    ) # True

    contains(
        {'a': 4, 'b': {'a':3, 'c': 5}},
        {'a': 3, 'c': 5}
    ) # True

    # if an `contained` has a list, then every item from that list must be present
    # in the corresponding `container` list
    contains(
        {'a': [{'b':1}, {'b':2}, {'b':3}], 'c':4},
        {'a': [{'b':1},{'b':2}], 'c':4},
    ) # True

    # You can also use the string literal 'ANYTHING' to match anything
        contains(
        {'a': [{'b':3}]},
        {'a': 'ANYTHING'},
    ) # True

    # You can use 'ANYTHING' as a dict key and it indicates to match the corresponding value anywhere
    # below the current point
    contains(
        {'a': [ {'x':1,'b1':{'b2':{'c':'SOMETHING'}}}]},
        {'a': {'ANYTHING': 'SOMETHING', 'x':1}},
    ) # True

    contains(
        {'a': [ {'x':1, 'b':'SOMETHING'}]},
        {'a': {'ANYTHING': 'SOMETHING', 'x':1}},
    ) # True

    contains(
        {'a': [ {'x':1,'b1':{'b2':{'c':'SOMETHING'}}}]},
        {'a': {'ANYTHING': 'SOMETHING', 'x':1}},
    ) # True
    '''
    ANYTHING = 'ANYTHING'
    if contained == ANYTHING:
        return True

    if container == contained:
        return True

    if isinstance(container, list):
        if not isinstance(contained, list):
            contained = [contained]
        true_count = 0
        for contained_item in contained:
            for item in container:
                if contains(item, contained_item):
                    true_count += 1
                    break
        if true_count == len(contained):
            return True

    if isinstance(contained, dict) and isinstance(container, dict):
        contained_keys = set(contained.keys())
        if ANYTHING in contained_keys:
            contained_keys.remove(ANYTHING)
            if not contains(container, contained[ANYTHING]):
                return False

        container_keys = set(container.keys())
        if len(contained_keys - container_keys) == 0:
            # then all the contained keys are in this container ~ recursive check
            if all(
                contains(container[key], contained[key])
                for key in contained_keys
            ):
                return True

    # well, we're here, so I guess we didn't find a match yet
    if isinstance(container, dict):
        for value in container.values():
            if contains(value, contained):
                return True

    return False
1
JnBrymn

Voici une comparaison qui fonctionne même si vous avez des listes dans les dictionnaires:

superset = {'a': 1, 'b': 2}
subset = {'a': 1}

common = { key: superset[key] for key in set(superset.keys()).intersection(set(subset.keys())) }

self.assertEquals(common, subset)
1
user1338062

Dans Python 3 et Python 2.7, vous pouvez créer une "vue d'élément" d'un dictée sans copier aucune donnée. Cela vous permet d'utiliser des opérateurs de comparaison pour tester une relation de sous-ensemble.

En Python 3, cela ressemble à:

# Test if d1 is a sub-dict of d2
d1.items() <= d2.items()

# Get items in d1 not found in d2
difference = d1.items() - d2.items()

En Python 2.7, vous pouvez utiliser la méthode viewitems() à la place de items() pour obtenir le même résultat.

En Python 2.6 et inférieur, votre meilleur choix est de parcourir les clés du premier dict et de vérifier leur inclusion dans le second.

# Test if d1 is a subset of d2
all(k in d2 and d2[k] == d1[k] for k in d1)
0
augurar