2012-02-17 3 views
55

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

Например, d1 = {'a':'2', 'b':'3'} и d2 = то же самое. d1 == d2 Результаты True. Но предположим, что d2 = то же самое плюс куча других вещей. Мой метод должен быть способен определить, есть ли d1 в d2, но Python не может делать это со словарями.

Контекст:

У меня есть класс Слово, и каждый объект имеет свойства, как word, definition, part_of_speech, и так далее. Я хочу иметь возможность вызвать метод фильтрации в основном списке этих слов, например Word.objects.filter(word='jump', part_of_speech='verb-intransitive'). Я не могу понять, как управлять этими ключами и значениями одновременно. Но это может иметь большую функциональность вне этого контекста для других людей.

ответ

63

Преобразуйте в пара элементов и проверьте на герметичность.

all(item in superset.items() for item in subset.items()) 

Оптимизация оставлена ​​как упражнение для читателя.

+9

Если значения Dict являются hashable, используя viewitems() является наиболее optimizied способом я могу думать:.' D1.viewitems() <= d2.viewitems() 'пробеги Timeit показал более 3-кратное повышение производительности.Если не хешировать, даже использование 'iteritems()' вместо 'items()' приводит к улучшению 1.2x. Это было сделано с использованием Python 2.7. – Chad

+12

я не думаю, что оптимизация должна быть оставлена читатель - я обеспокоен тем, что люди на самом деле используют это, не понимая, что он собирается создать копию superset.items() и перебирать ее для каждого элемента в подмножестве. –

+1

@ Chad Это гораздо лучший ответ и на самом деле не является требуют, чтобы элементы были хешируемыми. Я преобразовал это в [ответ wiki сообщества] (http: // st ackoverflow.com/a/41579450). – augurar

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

контекст:

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

это, вероятно, самый быстрый. –

18

для ключей и значений проверить использование: set(d1.items()).issubset(set(d2.items()))

, если вам нужно проверить только ключи: set(d1).issubset(set(d2))

+8

Первое выражение не будет работать, если какое-либо значение ни в одном из словарей не будет хешироваться. –

+1

+1 для покрытия обоих прецедентов с кратким, читаемым и точным кодом (проблемы с хэшированием в стороне) – Alex

+5

Второй пример можно немного сократить, удалив набор (d2), так как «issubset принимает любой итерабельный». http://docs.python.org/2/library/stdtypes.html#set – trojjer

3

Моя функция для тех же целей, делая это рекурсивно:

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]) 
      else: 
       assert real[pkey] == pvalue 
       result = True 
    except (AssertionError, KeyError): 
     result = False 
    return result 

В вашем примере, dictMatch(d1, d2) должен возвращать Правда, даже если d2 есть другие вещи в нем, плюс это также относится к более низким уровням:

d1 = {'a':'2', 'b':{3: 'iii'}} 
d2 = {'a':'2', 'b':{3: 'iii', 4: 'iv'},'c':'4'} 

dictMatch(d1, d2) # True 

Примечание: Там может быть даже лучшим решением, которое позволяет избежать пункта if type(pvalue) is dict и применяется даже в более широком диапазоне случаев (например, списки хэшей и т. д.). Также рекурсия здесь не ограничивается, поэтому используйте свой риск. ;)

27

Примечание для людей, которые нуждаются в этом для модульного тестирования: существует также метод assertDictContainsSubset() в классе TestCase Python.

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

Это однако не рекомендуется в 3.2, не знаю, почему, может быть, есть замена для него.

+25

было любопытно, нашел это в [что нового в 3.2] (http://docs.python.org/3/whatsnew/3.2.html): * Метод assertDictContainsSubset() был устаревшим, поскольку он был неверно реализован с аргументами в неправильном порядке. Это создало трудно отлаживаемые оптические иллюзии, в которых такие тесты, как TestCase(). AssertDictContainsSubset ({'a': 1, 'b': 2}, {'a': 1}) потерпит неудачу. * * (Раймонд Хеттингер.) * – Pedru

+1

Ждать, ожидается левая сторона, а правая сторона действительна ... Разве это не так? Единственное, что неправильно с этой функцией, - это то, что происходит в каком месте? – JamesHutchison

+0

Нет замены для этого ... – warvariuc

0

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

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 
7

Для полноты, вы также можете сделать это:

def is_subdict(small, big): 
    return dict(big, **small) == big 

Однако, я делаю никаких претензий вообще не в отношении скорости (или ее отсутствие) или читаемости (или его отсутствие).

+0

Замечание: Другие ответы, в которых упоминается 'small.viewitems() <= big.viewitems()', были многообещающими, но с одной оговоркой: если ваша программа также может быть использована на Python 2.6 (или даже ниже), 'd1.items() <= d2.items()' фактически сравнивают 2 списка кортежей без особого порядка, поэтому конечный результат, вероятно, не будет надежным. По этой причине я переключаюсь на ответ @blubberdiblub. Upvoted. – RayLuo

+0

Это очень круто. Мне жаль, что я не мог бы продвигаться не один раз! – GhostCat

19

В Python 3 вы можете использовать dict.items(), чтобы получить представление типа элементов dict. Вы можете использовать оператор <=, чтобы проверить, если один вид является «подмножество» другого:

d1.items() <= d2.items() 

В Python 2.7, используйте dict.viewitems() сделать то же самое:

d1.viewitems() <= d2.viewitems() 

В Python 2.6 и ниже вам нужно другое решение, например, с помощью all():

all(key in d2 and d2[key] == d1[key] for key in d1) 
+0

для python3 это становится 'd1.items() <= d2.items()' –

+0

Предостережение: если ваша программа потенциально может использоваться на Python 2.6 (или даже ниже), 'd1.items() <= d2.items() 'фактически сравнивают 2 списка кортежей без особого порядка, поэтому конечный результат, вероятно, не будет надежным. По этой причине я переключаюсь на ответ @blubberdiblub. – RayLuo

+0

@RayLuo Да, viewitems - это не то же самое, что и элементы. Отредактировано для уточнения. – augurar

2

Это, казалось бы, простой вопрос стоит мне пару часов в исследовании к функции ind 100% надежное решение, поэтому я задокументировал то, что нашел в этом ответе.

  1. «Pythonic союзник» говоря, small_dict <= big_dict будет самым интуитивным способом, но очень плохо, что он не будет работать. {'a': 1} < {'a': 1, 'b': 2}, похоже, работает в Python 2, но он не является надежным, потому что официальная документация явно вызывает его. Ищите поиск «Исходы, отличные от равенства, разрешаются последовательно, но не определяются иным образом». в this section. Не говоря уже о том, что сравнение 2 dicts в Python 3 приводит к исключению TypeError.

  2. Вторая наиболее интуитивная вещь small.viewitems() <= big.viewitems() для Python 2.7 только, и small.items() <= big.items() для Python 3. Но есть один нюанс: это потенциально глючит. Если ваша программа потенциально может быть использована на Python < = 2.6, ее d1.items() <= d2.items() фактически сравнивают 2 списка кортежей без особого порядка, поэтому конечный результат будет ненадежным и станет неприятной ошибкой в ​​вашей программе. Я не хочу писать еще одну реализацию для Python < = 2.6, но мне все еще не очень удобно, что мой код содержит известную ошибку (даже если она находится на неподдерживаемой платформе). Поэтому я отказываюсь от такого подхода.

  3. Я осесть с @blubberdiblub 's answer (Кредит идет к нему):

    def is_subdict(small, big): return dict(big, **small) == big

    Стоит отметить, что этот ответ основан на == поведении между dicts, который четко определен в официальном документе , поэтому должен работать в каждой версии Python. Ищите:

    • «Словари сравниваются равными тогда и только тогда, когда они имеют одинаковые пары (ключ, значение)». последнее предложение в this page
    • «Сопоставления (экземпляры dict) сравнивают одинаковые тогда и только тогда, когда они имеют одинаковые пары (ключ, значение). Сравнение поровну клавиш и элементов обеспечивает рефлексивность."В this page
Смежные вопросы