Подтвердить что ты не робот

В python, как работает следующий класс AutoVivification?

В поисках способа работы с вложенными словарями я нашел следующий код, отправленный nosklo, который я хотел бы объяснить.

class AutoVivification(dict):
    """Implementation of perl 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__. Я понимаю, что если словарь текущего уровня существует, попытка будет проходить и перейти к следующему словарю. Полагаю, что except запускается, когда есть KeyError, но я раньше не видел self. self обрабатывается как словарь, тогда как я думал, что self был экземпляром class AutoVivification... это он? Я никогда не назначал два раза подряд, как этот foo = man = choo, но подозреваю, что value указывает на self[item], а self[item] указывает на результат type(self). Но type(self) вернет что-то вроде этого: <class '__main__.AutoVivification'> не так ли? Я понятия не имею, какие дополнительные круглые скобки в конце есть. Поскольку я не знаю, как вызывается функция, я не понимаю, куда возвращается value.

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

Если вы хотите знать, что я пытаюсь сделать в своей программе с вложенными словарями: я пытаюсь хранить данные карты в астрономическом масштабе. Хотя я не могу создать словари/списки из 10 ^ 6 элементов, вложенных 4 раза (это будет 10 ^ 24 элемента!), Пространство в основном пустое, поэтому я могу полностью оставить пустые значения и назначать только там, где есть что-то. То, что меня колотило, было эффективным способом обработки словарей.

4b9b3361

Ответ 1

Строка за строкой:

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__(), и вместо этого мы создаем новое значение, сохраните его в self[item] и верните это значение.

Теперь помните, что self является (подклассом) dict, поэтому он является словарем. Таким образом, он может назначать новые значения (для которых он будет использовать __setitem__ hook, случайно), и в этом случае он создает новый экземпляр того же типа, что и self. Этот другой подкласс dict.

Итак, что происходит подробно, когда мы вызываем a[1][2][3] = 4? Python проходит этот шаг за шагом:

  • a[1] приводит к type(a).__getitem__(a, 1). Пользовательский __getitem__ метод AutoVivification ловит KeyError, создает новый экземпляр AutoVivification, сохраняет его под ключом 1 и возвращает его.

  • a[1] возвращает пустой экземпляр AutoVivification. Следующий объект доступа [2] вызывается на этом объекте, и мы повторяем, что произошло на шаге 1; существует KeyError, создается новый экземпляр AutoVivification, хранящийся под ключом 2, и этот новый экземпляр возвращается вызывающему.

  • a[1][2] возвращает пустой экземпляр AutoVivification. Следующий объект доступа [3] вызывается на этом объекте, и мы повторяем, что произошло на шаге 1 (и на шаге 2). Существует KeyError, создается новый экземпляр AutoVivification, хранящийся под ключом 3, и этот новый экземпляр возвращается вызывающему.

  • a[1][2][3] возвращает пустой экземпляр AutoVivification. Теперь мы сохраняем новое значение в этом экземпляре, 4.

Как только вы перейдете к следующей строке кода, a[1][3][3] = 5, экземпляр AutoVivification верхнего уровня уже имеет ключ 1, а строка return dict.__getitem__(self, item) вернет соответствующее значение, которое, как представляется, будет AutoVivification экземпляр, созданный на первом шаге.

Оттуда вызов доступа к элементу [3] снова создаст новый экземпляр AutoVivification (поскольку объект a[1] имеет только 2), и мы снова пройдем все те же шаги.

Ответ 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(), который должен выглядеть намного более знакомым.

Надеюсь, что это очистит вас!