Как проверить, равны ли 2 значения, кроме некоторых полей? - PullRequest
0 голосов
/ 01 октября 2018

Существует два режима: старый и обновленный.Я хочу проверить, равны ли они, кроме клавиш status, latitude и longitude.

assert old_dict['status'] != updated_dict['status']
assert old_dict['latitude'] != updated_dict['latitude']
assert old_dict['longitude'] != updated_dict['longitude']

for field in ('status', 'latitude', 'longitude'):
    updated_dict.pop(field)
    old_dict.pop(field)

assert old_dict == updated_dict

Какой более питонный способ сделать это?

Ответы [ 7 ]

0 голосов
/ 01 октября 2018

Как насчет

ignore = {'status', 'latitude', 'longitude'}
equal = all([old_value == new[key]
             for key, old_value in old.items()
             if key not in ignore])

Это повторяет диктовку один раз (я не думаю, что есть способ обойти это).

0 голосов
/ 02 октября 2018

Я бы предположил, что полный тест требует, чтобы исключение или excluded ключи ввода были разными, и что в двух словарях могут быть не все общие ключи.

Некоторые тестовые случаиможно написать так:

import string
import random

random.seed(0)

keys = list(string.ascii_letters)
excluded = 'r', 'm', 'e'

# the original dict
base_dict = {key: random.randint(1, 100) for key in keys}

# some keys, different from excluded are different
unequal_dict = {key: (val if key not in ('q') else random.randint(1, 100)) for key, val in base_dict.items()}

# only the excluded keys are different
equal_dict = {key: (val if key not in excluded else random.randint(1, 100)) for key, val in base_dict.items()}

# only some of the excluded keys are different
partial_dict = {key: (val if key not in excluded[1:] else random.randint(1, 100)) for key, val in base_dict.items()}

# a copy of the base dict
identical_dict = base_dict.copy()

# one more key is added
not_same_keys_dict = base_dict.copy()
not_same_keys_dict['aa'] = 1

, где сейчас old_dict в основном base_dict, тогда как unequal_dict, equal_dict, partial_dict, identical_dict и not_same_keys_dict охватывают различные угловые случаи.

Затем мы определяем некоторые вспомогательные функции для одновременного тестирования различных входных данных.

def multi_test(func, many_args):
    return [func(*args) for args in many_args]

many_args = (
    (base_dict, unequal_dict, updated),
    (base_dict, equal_dict, updated),
    (base_dict, partial_dict, updated),
    (base_dict, identical_dict, updated),
    (base_dict, not_same_keys_dict, updated))

Исходный функционализированный код выглядит следующим образом:

import copy

def dicts_equal_except_orig(dict1, dict2, excluded):
    dict1 = dict1.copy()
    dict2 = dict2.copy()
    result = True
    for key in excluded:
        result = result and (dict1[key] != dict2[key])
        dict1.pop(key)
        dict2.pop(key)
    result = result and (dict1 == dict2)
    return result

print(multi_test(dicts_equal_except_orig, many_args))
# [False, True, False, False, False]

%timeit multi_test(dicts_equal_except_orig, many_args)
# 13.1 µs ± 183 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

И этоПримерно так же быстро, как вы можете получить с произведенным тестом, при условии, что у советов для сравнения есть некоторые общие ключи.Все другие подходы существенно медленнее, хотя потенциально более чистые, и, возможно, даже могут быть быстрее в некоторых обстоятельствах, например, когда количество исключаемых ключей велико и т. Д. Кроме того, если сценарий использования not_same_key не требуется, т.е.у ключей всегда одни и те же ключи, тогда решения на основе all() будут быстрее, поскольку будут иметь явное короткое замыкание, и их можно преобразовать, изменив:

keys = dict1.keys() | dict2.keys()

на, например,

keys = dict1.keys()

и удаление других проверок работоспособности, таких как if key in dict1 and key in dict2.


Для полноты, я сообщаю обо всех других протестированных мной опциях:

мое собственное решение с явнымтестирование

def dicts_equal_except(dict1, dict2, excluded):
    keys = dict1.keys() | dict2.keys()
    return all(
        (dict1[key] != dict2[key] if key in excluded else dict1[key] == dict2[key])
        if key in dict1 and key in dict2 else False
        for key in keys)


print(multi_test(dicts_equal_except, many_args))
# [False, True, False, False, False]

%timeit multi_test(dicts_equal_except, many_args)
# 28.3 µs ± 186 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

функционирования решения @blhsing

def check_dict_except(dict1, dict2, excluded):
    return {k for k, _ in dict1.items() ^ dict2.items()} == set(excluded)

print(multi_test(check_dict_except, many_args))
# [False, True, False, False, False]

%timeit multi_test(check_dict_except, many_args)
# 30.8 µs ± 498 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

вариантов решения от @ L3viathan

def dicts_equal_all(dict1, dict2, excluded):
    keys = dict1.keys() | dict2.keys()
    return all((dict1[key] == dict2[key]) ^ (key in excluded) for key in keys)

print(multi_test(dicts_equal_all, many_args))
# [False, True, False, False, False]

%timeit multi_test(dicts_equal_all, many_args)
# 29.7 µs ± 316 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

и

def dicts_equal_all2(dict1, dict2, excluded):
    keys = dict1.keys() | dict2.keys()
    return all((dict1[key] != dict2[key]) == (key in excluded) for key in keys)

print(multi_test(dicts_equal_all2, many_args))
# [False, True, False, False, False]

%timeit multi_test(dicts_equal_all2, many_args)
# 29.9 µs ± 435 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

адаптация @jpp ответа:

def compare_dicts(dict1, dict2, excluded):
    filter_dict1 = {key: val for key, val in dict1.items() if key not in excluded}
    filter_dict2 = {key: val for key, val in dict2.items() if key not in excluded}
    excluded_dict1 = {key: dict1[key] for key in excluded if key in dict1}
    excluded_dict2 = {key: dict2[key] for key in excluded if key in dict2}
    return filter_dict1 == filter_dict2 and all(dict1[key] != dict2[key] if key in dict1 and key in dict2 else False for key in excluded)

print(multi_test(compare_dicts, many_args))
# [False, True, False, False, False]

%timeit multi_test(compare_dicts, many_args)
# 57.5 µs ± 960 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
0 голосов
/ 01 октября 2018

Несколько строк, но должно быть довольно быстро, потому что это не меняет структуру пары dict для сравнения.

EXC = {"status", "latitude", "longitude"}
saved = {}

for key in EXC:
   saved[key], updated_dict[key] = updated_dict[key], old_dict[key]
cmp = old_dict == updated_dict
for key in EXC:
   old_dict[key], updated_dict[key] = updated_dict[key], saved[key]
0 голосов
/ 01 октября 2018

Можно утверждать, что симметричная разница между предметами двух диктов состоит из трех клавиш:

assert {k for k, _ in old_dict.items() ^ updated_dict.items()} == {'status', 'latitude', 'longitude'}
0 голосов
/ 01 октября 2018

Вы можете отфильтровать два своих словаря с помощью словарного понимания и затем проверить на равенство:

def compare_dicts(d1, d2, exc_keys):
    dct1 = {k: v for k, v in d1.items() if k not in exc_keys}
    dct2 = {k: v for k, v in d2.items() if k not in exc_keys}
    return dct1 == dct2

assert compare_dicts(old_dict, updated_dict, {'status', 'latitude', 'longitude'})
0 голосов
/ 01 октября 2018

Немного неортодоксальное предложение, но выслушайте меня:

differing = {"status", "latitude", "longitude"}
assert all(
    (old_dict[key] != updated_dict[key]) == (key in differing)
    for key in old_dict
)

Для каждого key мы утверждаем, что значения отличаются, если и только если ключ является одним из отличающихся ключей.

0 голосов
/ 01 октября 2018

Попробуйте это:

old_dict.keys() == updated_dict.keys()

будет верно, если old_dict kyes - это подмножество update_dict клавиш.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...