Как утверждать, что dict содержит другой dict без assertDictContainsSubset в python?

Я знаю, что assertDictContainsSubset может сделать это в python 2.7, но по какой-то причине это устарело в python 3.2. Итак, есть ли способ утверждать, что dict содержит другой без assertDictContainsSubset?

Это кажется нехорошим:

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

любой другой хороший способ? Спасибо


person JerryCai    schedule 11.01.2014    source источник
comment
Ни одно из решений не так хорошо, как assertDictContainsSubset. Я не доволен, что это было удалено :(   -  person Gezim    schedule 18.07.2018


Ответы (8)


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

И наоборот:

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

Ограничение: значения словаря должны быть хэшируемыми.

person John1024    schedule 11.01.2014
comment
Это работает для меня, и это просто. Спасибо, а items() равно iteritems(), верно? - person JerryCai; 11.01.2014
comment
iteritems был удален из python3 где items возвращает итератор. Приведенный выше код работает как с python2, так и с python3. - person John1024; 11.01.2014
comment
Обратите внимание, работает ли это или нет, зависит от содержимого словарей: если в качестве значения используется что-то, что нельзя хешировать, например список, вы не сможете создать set. - person DSM; 11.01.2014
comment
Это хорошо для модульного тестирования, но не подходит для производственного кода по нескольким причинам. 1. Он создает совершенно новые структуры данных набора для элементов dict, тратя время и память. 2. Это работает, только если словарь values, а также ключи хэшируются. - person augurar; 01.03.2019
comment
Вопрос был о assertDictContainsSubset, которая является удобной функцией unittest, поэтому подходит решение, которое работает для модульного тестирования. - person Matt Zimmerman; 01.08.2019


Большая проблема с принятым ответом заключается в том, что он не работает, если у вас есть нехешируемые значения в ваших значениях объектов. Во-вторых, вы не получаете полезного вывода — тест проходит или не проходит, но не сообщает вам, какое поле внутри объекта отличается.

Таким образом, проще просто создать словарь подмножества, а затем протестировать его. Таким образом, вы можете использовать метод TestCase.assertDictEquals(), который даст вам очень полезный форматированный вывод в вашем тестировщике, показывающий разницу между фактическим и ожидаемым.

Я думаю, что самый приятный и питонический способ сделать это с помощью простого понимания словаря как такового:

from unittest import TestCase


actual = {}
expected = {}

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

ПРИМЕЧАНИЕ. Очевидно, что если вы запускаете свой тест в методе, принадлежащем дочернему классу, который наследуется от TestCase (как вы почти наверняка должны быть), то это просто self.assertDictEqual(subset, expected)

person Sam Redway    schedule 24.11.2017
comment
Обратите внимание, что это не приведет к ошибке, если expected содержит ключ, которого нет в actual. - person Code-Apprentice; 20.12.2018
comment
Похоже будет? Подмножество не может быть равно ожидаемому, если ключ не является фактическим, поскольку подмножество генерируется из ключей в фактическом... - person Sam Redway; 20.12.2018

Решение John1024 сработало для меня. Однако в случае сбоя он только сообщает вам False вместо того, чтобы показывать вам, какие ключи не совпадают. Итак, я попытался избежать устаревшего метода утверждения, используя другие методы утверждения, которые будут выводить полезные сообщения об ошибках:

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

Это отвечает на более широкий вопрос, чем вы задаете, но я использую это в своих тестовых программах, чтобы увидеть, содержит ли словарь container что-то похожее на словарь contained. Это проверяет ключи и значения. Кроме того, вы можете использовать ключевое слово 'ANYTHING', чтобы указать, что вам все равно, как оно соответствует.

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
person JnBrymn    schedule 02.05.2016

В Python 3 и Python 2.7 вы можете создать «представление элемента» в виде набора без копирования каких-либо данных. Это позволяет использовать операторы сравнения для проверки отношения подмножества.

В Python 3 это выглядит так:

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

В Python 2.7 вы можете использовать метод viewitems() вместо items() для достижения того же результата.

В Python 2.6 и ниже лучше всего перебирать ключи в первом словаре и проверять их на включение во второй.

# Test if d1 is a subset of d2
all(k in d2 and d2[k] == d1[k] for k in d1)
person augurar    schedule 01.03.2019
comment
Спасибо за отличный ответ! Для Python 3.x кажется, что этот ответ должен быть принятым ответом, то есть a.items() <= b.items(). Хотя я также проголосовал за ответ @kepler (он пришел пару лет назад), объяснение до версии 2.7 полезно! - person dancow; 18.11.2020

Вы можете использовать assertGreaterEqual или assertLessEqual.

users = {'id': 28027, 'email': '[email protected]', 'created_at': '2005-02-13'}
data = {"email": "[email protected]"}

self.assertGreaterEqual(user.items(), data.items())
self.assertLessEqual(data.items(), user.items())  # Reversed alternative

Обязательно укажите .items(), иначе это не сработает.

person Bedram Tamang    schedule 30.07.2019
comment
Хм, попытка сделать это приводит к: TypeError: '>=' not supported between instances of 'dict' and 'dict' - person Danny Staple; 23.09.2020
comment
@DannyStaple Используйте с .items(). - person Acumenus; 16.04.2021

Вот сравнение, которое работает, даже если у вас есть списки в словарях:

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)
person user1338062    schedule 16.05.2015