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. Вы должны проверить, существует ли ключ в словаре...