В 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 элемента!), Пространство в основном пустое, поэтому я могу полностью оставить пустые значения и назначать только там, где есть что-то. То, что меня колотило, было эффективным способом обработки словарей.
Ответы
Ответ 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()
, который должен выглядеть намного более знакомым.
Надеюсь, что это очистит вас!