Python: Как обновить значение пары ключ-значение во вложенном словаре?

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

Я использовал этот ответ для второго порядка создания вложенного словаря. Предоставленное решение работает нормально, но с одной проблемой.

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

вывод должен в конечном итоге выглядеть следующим образом:

word1, {doc1 : freq}, {doc2 : freq} <br>
word2, {doc1 : freq}, {doc2 : freq}, {doc3:freq}
etc....

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

  File "scriptV3.py", line 45, in main
    freq = dictionary[keyword][filename] + 1
TypeError: unsupported operand type(s) for +: 'AutoVivification' and 'int'

Я думаю, что мне нужно каким-то образом привести экземпляр AutoVivification к int....

Как пройти?

заранее спасибо

мой код:

#!/usr/bin/env python 
# encoding: utf-8

import sys
import os
import re
import glob
import string
import sets

class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

def main():
    pad = 'temp/'
    dictionary  = AutoVivification()
    docID = 0
    for files in glob.glob( os.path.join(pad, '*.html') ):  #for all files in specified folder:
        docID = docID + 1
        filename = "doc_"+str(docID)
        text = open(files, 'r').read()                      #returns content of file as string
        text = extract(text, '<pre>', '</pre>')             #call extract function to extract text from within <pre> tags
        text = text.lower()                                 #all words to lowercase
        exclude = set(string.punctuation)                   #sets list of all punctuation characters
        text = ''.join(char for char in text if char not in exclude) # use created exclude list to remove characters from files
        text = text.split()                                 #creates list (array) from string
        uniques = set(text)                                 #make list unique (is dat handig? we moeten nog tellen)

        for keyword in uniques:                             #For every unique word do   
            for word in text:                               #for every word in doc:
                if (word == keyword and dictionary[keyword][filename] is not None): #if there is an occurence of keyword increment counter 
                    freq = dictionary[keyword][filename]    #here we fail, cannot cast object instance to integer.
                    freq = dictionary[keyword][filename] + 1
                    print(keyword,dictionary[keyword])
                else:
                    dictionary[word][filename] = 1

#extract text between substring 1 and 2 
def extract(text, sub1, sub2): 
    return text.split(sub1, 1)[-1].split(sub2, 1)[0]    

if __name__ == '__main__':
    main()

9 ответов

Можно использовать Python collection.defaultdict вместо создания класса AutoVivification и затем создания словаря в качестве объекта этого типа.

import collections
dictionary = collections.defaultdict(lambda: collections.defaultdict(int))

Это создаст словарь словарей со значением по умолчанию, равным 0. Когда вы хотите увеличить запись, используйте:

dictionary[keyword][filename] += 1

Я согласен, что вы должны избегать дополнительных занятий, и особенно __getitem__, (Небольшие концептуальные ошибки могут сделать __getitem__ или же __getattr__ довольно больно отлаживать.)

питон dict кажется достаточно сильным для того, что вы делаете.

Как насчет прямой dict.setdefault

    for keyword in uniques:                             #For every unique word do   
        for word in text:                               #for every word in doc:
            if (word == keyword):
                dictionary.setdefault(keyword, {})
                dictionary[keyword].setdefault(filename, 0)
                dictionary[keyword][filename] += 1

Конечно, это будет где dictionary это просто dict, а не что-то из collections или ваш собственный класс.

Опять же, разве это не просто:

        for word in text:                               #for every word in doc:
            dictionary.setdefault(word, {})
            dictionary[word].setdefault(filename, 0)
            dictionary[word][filename] += 1

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

В классе AutoVivification вы определяете

value = self[item] = type(self)()
return value

который возвращает экземпляр себя, который является AutoVivification в этом контексте. Ошибка становится понятной.

Вы уверены, что хотите вернуть автовивификацию для любого недостающего ключевого запроса? Из кода я бы предположил, что вы хотите вернуть обычный словарь со строковым ключом и значениями int.

Кстати, может быть, вас заинтересует класс defaultdict.

Было бы лучше пнуть AutoVivification все вместе, потому что это ничего не добавляет.

Следующая строка:

if (word == keyword and dictionary[keyword][filename] is not None):

Не работает, как ожидалось, из-за того, как работает ваш класс, dictionary[keyword] всегда будет возвращать экземпляр AutoVivificationи так будет dictionary[keyword][filename],

Этот класс AutoVivification не та магия, которую вы ищете.

Проверять, выписываться collections.defaultdict из стандартной библиотеки. Ваши внутренние диктовки должны быть defaultdicts, которые по умолчанию являются целочисленными значениями, а ваши внешние dicts будут defaultdicts, которые по умолчанию принимают значения inner-dict.

#!/usr/bin/env python
# encoding: utf-8
from os.path import join
from glob import glob as glob_
from collections import defaultdict, Counter
from string import punctuation

WORKDIR  = 'temp/'
FILETYPE = '*.html'
OUTF     = 'doc_{0}'.format

def extract(text, startTag='<pre>', endTag='</pre>'):
    """Extract text between start tag and end tag

    Start at first char following first occurrence of startTag
      If none, begin at start of text
    End at last char preceding first subsequent occurrence of endTag
      If none, end at end of text
    """
    return text.split(startTag, 1)[-1].split(endTag, 1)[0]    

def main():
    DocWords = defaultdict(dict)

    infnames = glob_(join(WORKDIR, FILETYPE))
    for docId,infname in enumerate(infnames, 1):
        outfname = OUTF(docId)
        with open(infname) as inf:
            text = inf.read().lower()
        words = extract(text).strip(punctuation).split()
        for wd,num in Counter(words).iteritems():
            DocWords[wd][outfname] = num

if __name__ == '__main__':
    main()

Я думаю, что вы пытаетесь добавить 1 к записи словаря, которая еще не существует. Ваш метод getitem по какой-то причине возвращает новый экземпляр класса AutoVivification в случае сбоя поиска. Поэтому вы пытаетесь добавить 1 к новому экземпляру класса.

Я думаю, что ответ заключается в том, чтобы обновить метод getitem, чтобы он устанавливал счетчик на 0, если он еще не существует.

class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            self[item] = 0
            return 0

Надеюсь это поможет.

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

идентификатор документа -> [word_ids]

и обратное отображение индекса

word_id -> [document_ids]

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

if (word == keyword and dictionary[keyword][filename] is not None): 

это не правильное использование, я думаю, вместо этого попробуйте это:

if (word == keyword and filename in dictionary[keyword]): 

Потому что, проверяя значение несуществующего ключа, поднимаем KeyError. Вы должны проверить, существует ли ключ в словаре...

Другие вопросы по тегам