PERL-подобная автовивификация со значением по умолчанию в Python и возвращает значение по умолчанию из несуществующей произвольной вложенности?

Предположим, мне нужна PERL-подобная автовивикация в Python, т.е.:

>>> d = Autovivifier()
>>> d = ['nested']['key']['value']=10
>>> d
{'nested': {'key': {'value': 10}}}

Есть несколько основных способов сделать это:

  1. Использовать рекурсивный словарь по умолчанию
  2. Используйте хук __missing__ для возврата вложенной структуры

ОК -- легко.

Теперь предположим, что я хочу вернуть значение по умолчанию из словаря с отсутствующим ключом. Еще раз, несколько способов сделать это:

  1. Для невложенного пути вы можете использовать хук __missing__
  2. try/except блокирует доступ к потенциально отсутствующему ключевому пути
  3. Используйте {}.get(key, default) (трудно работать с вложенным словарем), т. е. Нет версии autoviv.get(['nested']['key']['no key of this value'], default)

Две цели кажутся непримиримыми конфликтами (судя по тому, что я пытался разобраться в этом последние пару часов).

Вот вопрос:

Предположим, я хочу иметь Autovivifying dict, который 1) создает вложенную структуру для d['arbitrary']['nested']['path']; И 2) возвращает значение по умолчанию из несуществующего произвольного вложения без переноса его в try/except?

Вот проблемы:

  1. Вызов d['nested']['key']['no key of this value'] эквивалентен (d['nested'])['key']['no key of this value']. Переопределение __getitem__ не работает без возврата объекта, который ТАКЖЕ переопределяет __getitem__.
  2. Оба метода создания Autovivifier создадут запись в словаре, если вы проверите существование этого пути. то есть я не хочу, чтобы if d['p1']['sp2']['etc.'] создавал весь этот путь, если вы просто протестируете его с помощью if.

Как я могу предоставить dict в Python, который будет:

  1. Создать путь доступа типа d['p1']['p2'][etc]=val (Автовивикация);
  2. НЕ создавайте тот же путь, если вы проверяете его существование;
  3. Вернуть значение по умолчанию (например, {}.get(key, default)) без переноса в try/except
  4. Мне не нужен ПОЛНЫЙ набор операций dict. На самом деле только d=['nested']['key']['value']=val и d['nested']['key']['no key of this value'] равны значению по умолчанию. Я бы предпочел, чтобы тестирование d['nested']['key']['no key of this value'] не создавало его, но согласился бы с этим.

?


person dawg    schedule 30.06.2014    source источник
comment
для чего вам это нужно? возможно, необязательные вложенные словари - не лучшая структура данных?   -  person Eevee    schedule 30.06.2014
comment
Можете ли вы объяснить свой пункт № 3? Вы говорите, что хотите иметь возможность делать это вложенным способом? Если да, то как? Каково фактическое использование с get, которое вы хотите сделать?   -  person BrenBarn    schedule 30.06.2014
comment
@Eevee: Иерархические данные, такие как animals={'amphibian':{'Bufoides':['Mawblang toad','Khasi Hills toad'], more amphibians...}, 'Insects':{'Ants':{'Acanthognathus'}[list of Acanthognathus]}}, где разные записи могут вкладываться глубже, чем другие ... Также - любопытство!   -  person dawg    schedule 30.06.2014
comment
@BrenBarn: Под пунктом 3 вы имеете в виду Return a default value (like {}.get(key, default)) without wrapping in try/except? Если это так, я не видел рационального случая, когда я мог бы сделать val=autoviv.get(['nested']['key']['no key of this value'], default) {}.get не поддерживает рекурсию...   -  person dawg    schedule 30.06.2014
comment
@dawg: вы не сможете заставить работать именно этот синтаксис, потому что вы не можете указать индексирование с помощью [] с помощью get. Возможно, вы могли бы получить приближение, используя что-то вроде get(['nested', 'key', 'no key'], default), где вы указываете последовательность ключей.   -  person BrenBarn    schedule 30.06.2014
comment
@BrenBarn: мне не нужно специально {}.get() для работы; Я использую это как аналогию.   -  person dawg    schedule 30.06.2014
comment
Было бы полезно, если бы вы могли указать фактический набор операций, которые вы хотите выполнять с этим объектом. Например, вам нужно иметь возможность делать d['one']['two']['three'] и возвращать значение по умолчанию без создания промежуточных объектов, или вам просто нужно иметь возможность вызывать d.get(['one', 'two', 'three'], default)?   -  person BrenBarn    schedule 30.06.2014


Ответы (3)


Чтобы создать рекурсивное дерево словарей, используйте defaultdict с хитростью:

from collections import defaultdict

tree = lambda: defaultdict(tree)

Затем вы можете создать свой x с помощью x = tree().

выше из @BrenBarn -- defaultdict of defaultdict, вложенный

person johntellsall    schedule 30.06.2014
comment
Да, я связался с аналогичным ответом в своем вопросе. Это работает для tree[1][2][3]=4 Но как только вы создадите экземпляр tree, теперь попробуйте несуществующий рекурсивный путь для проверки существования, например if tree['does']['not']['exist'] - он возвращает пустую фабрику словарей по умолчанию И создает путь, который вы проверяли на существование. - person dawg; 30.06.2014

Не делай этого. Эту проблему можно было бы решить гораздо проще, просто написав класс с нужными вам операциями, и даже в Perl это не общепризнанная функция.

Но это возможно с помощью специального класса autoviv. Вам понадобится __getitem__, который возвращает пустой словарь autoviv, но не сохраняет его. Новый словарь autoviv запомнит словарь autoviv и создавший его ключ, а затем вставит себя в родительский элемент только тогда, когда в нем будет сохранено «реальное» значение.

Поскольку пустой словарь проверяется как ложный, вы можете проверить его существование в стиле Perl, фактически не создавая промежуточных словарей.

Но я не собираюсь расписывать код, потому что уверен, что это ужасная идея.

person Eevee    schedule 30.06.2014
comment
It could be solved much more easily by just writing a class that has the operations you want... Я готов написать курс; Я просто не вижу класс, чтобы писать. Я пробовал подкласс dict и подкласс defaultdict. Я думаю, что эта идиома распространена в Perl - по крайней мере, когда несколько лет назад это был мой язык по умолчанию... - person dawg; 30.06.2014
comment
учтите, что, возможно, ваши ключи должны быть сами пути, например. 'one', 'two', 'three', а не разбивать пути на множество отдельных словарей. - person Eevee; 30.06.2014
comment
@Eevee: Использование кортежей - известное решение. Однако это действительно отстой с большими наборами данных и глубокими деревьями, фактически вы сохраняете всю глубину дерева для каждого листа. С глубокими деревьями вы можете умножать объем данных на большую константу (пропорциональную глубине), и даже мелкие деревья могут потребовать использования неинтуитивного кодирования для сжатия путей. - person Rich Farmbrough; 23.02.2021

Хотя это не совсем соответствует протоколу словаря в Python, вы можете добиться разумных результатов, реализовав свой собственный словарь авто-оживления, который использует переменные аргументы getitem. Что-то вроде (2.x):

class ExampleVivifier(object):
    """ Small example class to show how to use varargs in __getitem__. """

    def __getitem__(self, *args):
        print args

Пример использования:

>>> v = ExampleVivifier()
>>> v["nested", "dictionary", "path"]
(('nested', 'dictionary', 'path'),)

Вы можете заполнить пробелы, чтобы увидеть, как вы можете добиться желаемого поведения здесь.

person Brett Lempereur    schedule 30.06.2014