繁体   English   中英

在 Python3 中嵌套默认字典

[英]Nesting Defaultdictionaries in Python3

我正在编写一段代码,这是我第一次从collections实现defaultdict 目前,我有一段代码可以很好地用作defaultdict ,但我真的很想嵌套我的字典。

这是我目前拥有的代码:

from collections import defaultdict, Counter
from re import findall

class Class: 
    def __init__(self, n, file):
        self.counts = defaultdict(lambda: defaultdict(int))
        self.__n = n
        self.__file = file

    def function(self, starting_text, charas):
        self.__starting_text = starting_text
        self.__charas = charas

        with open(self.__file) as file:
            text = file.read().lower().replace('\n', ' ')

        ngrams = [text[i : i + self.__n] for i in range(len(text))]

        out = self.counts
        for item in ngrams:
            data = []
            for word in findall( item+".", text):
                data.append(word[-1])
            self.counts = { item : data.count(item) for item in data }
            out[item] = self.counts
        self.counts = out

代码中的某些内容尚未实现,因为我有点停滞不前,因此请忽略任何不适用于此特定问题的内容!

如果我在最后运行print(self.counts) ,我的程序会运行如下所示的内容:

defaultdict(<function Class.__init__.<locals>.<lambda> at 0x7f8c4a94bea0>, {'t': {'h': 1, ' ': 1, 'e': 1}, 'h': {'i': 1, 'o': 1}, 'i': {'s': 2}, 's': {' ': 2, 'h': 1, 'e': 1}, ' ': {'i': 1, 'a': 1, 's': 2}, 'a': {' ': 1}, 'o': {'r': 1}, 'r': {'t': 1}, 'e': {'n': 2, ' ': 1}, 'n': {'t': 1, 'c': 1}, 'c': {'e': 1}})

这很棒! 但我真的很想让那些内部字典也成为 defaultdicts。 特别是,如果我运行self.counts['t']['h'] ,我会得到1 ,正如预期的那样。 但是, defaultdict 的一个好处是,如果密钥不可用,它会给您0 目前,如果我运行self.counts['t']['x']我会得到一个 keyerror,但我希望通过让每个内部列表也成为 defaultdict 来获得0

我假设这可以在以out=self.counts开头的代码块中的某个地方完成,但我有点不确定如何实现这一点。

  1. 使数据成为collections.Counterdefaultdict而不是列表(因为您完全不关心位的顺序,所以只计算它们的出现次数)
  2. 然后用计数器update你的self.counts[item]而不是分配一个字典
  3. 你甚至可以直接进行更新:
     for item in ngrams: data = self.counts[item] for word in findall( item+".", text): data[word[-1]] += 1
    就是这样,这会将相关计数直接更新为最初定义的 defaultdict

除了大部分代码......不理想,或奇怪

看起来不必要地复杂

您在 ngram 之后获取每个代码点,为什么不直接提取 n+1 的伪 ngram 并将其拆分? 类似于(未经测试,可能略有偏差):

for i in range(0, len(text)-n):
    ngram, follower = text[i:i+n], text[i+n]
    self.counts[ngram][follower] += 1

这也避免了代码的至少二次复杂性(以及各种常数复杂性),这是一个很好的副作用,但请注意,原始代码会隐式跳过\\n (换行符/换行符)的跟随者,而无需re.DOTALL. “匹配除换行符以外的任何字符”。 因此,如果您想保持这种行为,则必须专门测试并跳过follower == '\\n'

将成员变量重用为局部变量?

您出于某种奇怪的原因将self.counts重用为局部变量,将其保存到out ,将其设置为奇怪的东西,然后在将其设置为自身后重新加载它,为什么不是out内部变量?

        for item in ngrams:
            data = []
            for word in findall( item+".", text):
                data.append(word[-1])
            out = { item : data.count(item) for item in data }
            self.counts[item] = out

这并不是很有用(可能除了 printf 调试实用程序),您可以直接分配给self.counts[item]

我也不知道__starting_text__charas有什么实用程序

双下划线前缀

别。 不管你用它们做什么,我有理由肯定你是错的(因为我很少遇到知道这些是什么的人),你应该停止它。

如果您想向调用者提示某些内容是对象的内部细节,请使用单个下划线前缀。 尽管您可能也不需要这样做。

始终将编码传递给文本模式open

严重地。 open(path)在文本模式下工作(自动将原始磁盘数据解码为str ),但它选择的编码是getdefaultencoding()返回的任何getdefaultencoding() ,这很可能不是垃圾。 你不想用它来读取用户的文件,你真的绝对不想用它来读取你自己的文件。 明确提供encoding='utf-8' ,它将避免大量的悲伤。 如果您需要推断编码,请使用 chardet。

您应该更改这两行:

self.counts = { item : data.count(item) for item in data }
out[item] = self.counts

您不需要out变量。 问题是您正在创建一个“正常”的字典并将其分配给self.count 只需使用:

self.counts[item].update({ item : data.count(item) for item in data })

您的代码没有使用defaultdict任何功能。 如果您改用普通的dict它的工作方式相同。 这也非常令人困惑,因为您在代码的不同部分为不同的事物使用了多个变量名称(如itemself.__counts )。

但是您可以非常简单地将其更改为使用defaultdict而不是以艰难的方式做事。 这是我修复它的方法:

def function(self, starting_text, charas):
    self.__starting_text = starting_text
    self.__charas = charas

    with open(self.__file) as file:
        text = file.read().lower().replace('\n', ' ')

    ngrams = set(text[i : i + self.__n] for i in range(len(text)))

    for item in ngrams:
        for word in findall( item+".", text):
            self.counts[item][word[-1]] += 1

这与您之前的代码不完全相同,因为它不是幂等的(如果您重复调用它,您将不断添加计数,而不是用新计数替换旧计数)。 您可能可以通过首先将所有字符放入列表(如您的data ),然后仅在最后设置self.__counts的值来恢复大部分旧行为。 但是我可能更喜欢使用collections.Counter而不是手工操作(我可能会在循环中构建Counter ,而不是让defaultdict为我做)。

在一个不相关的说明中:您的代码在您的几个属性上使用双前导下划线。 对于 Python 代码,通常不鼓励这样做。 它启用名称修改,将像self.__n这样的名称转换为self._Class__n (如果Class是使用__n的代码写入的类的名称,无论self是什么类型)。 它通常不应该用于将某些内容标记为“私有”,而是在您无法提前知道哪些其他名称可能会放入同一对象命名空间时避免意外的名称冲突。 例如,代理或 mixin 类可能需要允许用户访问任何类型的属性名称,而类的设计者无法知道它们将是什么(以及将被代理的对象或类的设计者)将与可能不知道代理/mixin 类的存在混合)。 如果您只想将您的属性标记为“私有”,请使用单个前导下划线而不是两个。 Python 中没有强制执行数据隐私,尝试使用名称修饰只会误导您(想要访问您的属性的外部代码仍然能够这样做)。 名称修改使调试变得更加困难。 Python 的哲学是它的程序员都是“同意的成年人”,因此应该相信他们知道不会对其他代码的内部行为不端(或者如果他们这样做的话,要处理后果)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM