Python:如何在嵌套字典中更新键值对的值?

时间:2011-02-22 15:00:32

标签: python object casting autovivification

我正在尝试创建一个反向的文档索引,因此我需要从集合中的所有独特单词中了解它们发生在哪些文档中以及发生的频率。

我使用了this回答,以便两个人创建一个嵌套字典。提供的解决方案工作正常,但有一个问题。

首先,我打开文件并列出一个独特的单词列表。这些独特的单词我想要与原始文件进行比较。当匹配时,应更新频率计数器,并将其值存储在二维数组中。

输出最终应如下所示:

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 个答案:

答案 0 :(得分:6)

可以使用Python的collections.defaultdict而不是创建AutoVivification类,然后将字典实例化为该类型的对象。

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

这将创建一个字典字典,默认值为0.如果要增加条目,请使用:

dictionary[keyword][filename] += 1

答案 1 :(得分:2)

我同意你应该避免额外的课程,特别是__getitem__。 (小的概念错误可能会导致__getitem____getattr__调试非常痛苦。)

Python 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无论如何强制使用唯一的键。

答案 2 :(得分:0)

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

我认为这不是正确的用法,而是试试这个:

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

因为,检查不存在的键的值会引发KeyError。 :所以你必须检查字典中是否存在密钥......

答案 3 :(得分:0)

我认为您正在尝试将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

希望这会有所帮助。

答案 4 :(得分:0)

不知道为什么你需要嵌套的dicts。在典型的索引方案中,您有一个正向索引映射

文件ID - &gt; [word_ids]

和反向索引映射

word_id - &gt; [document_ids]

不确定这是否与此相关,但使用两个索引可以执行所有类型的查询 由于您不需要处理,因此非常有效并且实施非常直接 使用嵌套数据结构。

答案 5 :(得分:0)

在AutoVivification类中,您可以定义

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

返回self的实例,该实例是该上下文中的AutoVivification。然后,错误变得清晰。

您确定要在任何缺少的密钥查询上返回AutoVivification吗?从代码中,我假设你想要返回一个包含字符串键和int值的普通字典。

顺便说一下,也许你会对defaultdict课感兴趣。

答案 6 :(得分:0)

最好将AutoVivification放在一起,因为它不会增加任何内容。

以下一行:

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

由于您的课程的工作方式不能正常工作,dictionary[keyword]将始终返回AutoVivification的实例,dictionary[keyword][filename]也是如此。

答案 7 :(得分:0)

这个AutoVivification类并不是你想要的魔力。

从标准库中查看collections.defaultdict。你的内部dicts应该是默认为整数值的默认值,而你的外部dicts则是默认为inner-dict值的默认值。

答案 8 :(得分:0)

#!/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()
相关问题