在python中,以下AutoVivification类如何工作?

时间:2012-11-07 18:52:10

标签: python dictionary autovivification

在搜索嵌套词典的使用方法时,我发现nosklo发布了以下代码,我想解释一下。

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

测试:

a = AutoVivification()

a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6

print a

输出:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}

我是一个非常新手的程序员。我在自己的时间里学到了大部分我所知道的知识,我在高中时只接受过Turbo Pascal的正式训练。我理解并能够以简单的方式使用类,例如使用__init__,类方法,并使用foo.man = 'choo'在类的实例中存储数据。

我不知道方括号系列是如何通过类正确定向的(我假设它们以某种方式调用__getitem__)并且不明白它们如何在不必调用的情况下如此简洁地处理该方法分别三次。

我的印象是,类声明中的(dict)将由__init__处理。

之前我曾经以非常简单的方式使用try: except:。它看起来像try,当它运行时,它调用一系列函数__getitem__。我知道如果当前级别的字典存在,则try将通过并转到下一个字典。我收集的except会在KeyError时运行,但我之前没有看到self使用过的Selfself被视为字典,而我认为class AutoVivificationfoo = man = choo的一个实例......是吗?我从未连续两次分配此value,但怀疑self[item]指向self[item]type(self)指向type(self)的结果。但是<class '__main__.AutoVivification'>会返回这样的内容:value不是吗?我不知道最后有什么额外的圆括号。因为我不知道函数是如何被调用的,所以我不明白返回{{1}}的位置。

对不起所有问题!有这么多,我不明白,我不知道在哪里查阅,只需阅读文档几个小时,我保留很少。这段代码看起来似乎符合我的目的,但我想在使用之前理解它。

如果您想知道我在程序中尝试使用嵌套字典做什么:我正在尝试按天文尺度保存地图数据。虽然我无法创建嵌套4次的10 ^ 6项的字典/列表(也就是10 ^ 24项!),但空间大部分都是空的,所以我可以完全保留空值,只有在那里有东西时才分配。困扰我的是处理字典的有效方式。

2 个答案:

答案 0 :(得分:19)

逐行:

class AutoVivification(dict):

我们创建了dict的子类,因此AutoVivification是一种dict,有一些本地更改。

def __getitem__(self, item):

只要有人试图通过[...]索引查找来访问实例上的项目,就会调用__getitem()__ hook。因此,只要有人object[somekey],就会调用type(object).__getitem__(object, somekey)

我们暂时跳过try,下一行是:

 return dict.__getitem__(self, item)

这会调用未绑定的方法__getitem__(),并将自己的实例与密钥一起传递给它。换句话说,我们将父类__getitem__定义的原始 dict称为。

现在,我们都知道如果字典中没有item键,会引发KeyError会发生什么。这是try:except KeyError组合的来源:

    try:
        return dict.__getitem__(self, item)
    except KeyError:
        value = self[item] = type(self)()
        return value

因此,如果当前实例(dict的子类型)没有给定键,它将捕获原始KeyError方法的dict.__getitem__()异常抛出,而我们创建一个 new 值,将其存储在self[item]中并返回该值。

现在,请记住selfdict的(子类),所以它是一本字典。因此,它可以分配新值(它将在其中使用__setitem__ hook,并且在这种情况下,它会创建与self相同类型的 new 实例。那是另一个dict子类。

那么当我们致电a[1][2][3] = 4时会发生什么? Python逐步完成了这一步:

  1. a[1]会导致type(a).__getitem__(a, 1)__getitem__的自定义AutoVivification方法捕获KeyError,创建{em}新 AutoVivification实例,将其存储在密钥1下并将其归还。

  2. a[1]返回了一个空的AutoVivification实例。在该对象上调用下一个访问[2]的项目,我们重复步骤1中发生的事情;有KeyError,创建了AutoVivification的新实例,存储在2项下,并且新实例将返回给调用者。

  3. a[1][2]返回了一个空的AutoVivification实例。在该对象上调用下一个访问[3]的项目,我们重复步骤1(以及步骤2)中发生的事情。有KeyError,创建了AutoVivification的新实例,存储在3项下,并且新实例将返回给调用者。

  4. a[1][2][3]返回了一个空的AutoVivification实例。现在我们在该实例中存储一个新值4

  5. 进入下一行代码a[1][3][3] = 5后,顶级AutoVivification实例已有1个密钥,return dict.__getitem__(self, item)行将返回相应的值,恰好是上面第一步中创建的AutoVivification实例。

    从那里,[3]项访问调用将再次创建一个新的AutoVivification实例(因为a[1]处的对象只有一个2密钥),我们去了再次通过所有相同的步骤。

答案 1 :(得分:2)

请参阅object.__getitem__文档,了解一下。

class AutoVivification(dict)声明使AutoVivification成为dict的子类,因此它的行为与dict的行为相同,除非它明确地覆盖了某些行为 - 正如此类所做的那样当它覆盖__getitem__

dict.__getitem__(self, item)的调用通常会改为:

super(AutoVivification, self).__getitem__(item)

(至少在Python 2.x中; Python 3有更好的语法。)无论哪种方式,它的作用是尝试让默认的dict行为运行,但是在不起作用的情况下实现回退。

type(self)()首先查找与self实例对应的类对象,然后调用类对象 - 在这种情况下与编写AutoVivification()相同,应该看起来更为熟悉。

希望能为你清理它!