Как мне выполнить расширенную автовивитацию хэша Python?

Этот вопрос касается реализации полной автовивиляции Perl в Python. Я знаю, что подобные вопросы задавали до и до сих пор лучший ответ в "Каков наилучший способ реализации вложенных словарей в Python?". Однако я хочу сделать это:

a['x']['y'].append('z')

без объявления a['x']['y'] = [] сначала, вернее, не объявляя a['x'] = {}. (Примечание в Perl вы можете сделать push @{$a->{x}{y}}, 'z';.)

Я знаю, что dict и list классы sorta не смешиваются, поэтому это сложно, но мне интересно узнать, есть ли у кого-то гениальное решение, связанное с созданием унаследованного класса из dict, но определяющим новый метод append на нем?

Я также знаю, что это может отбросить некоторых пуристов Python, которые попросят меня придерживаться Perl. Но, даже просто для вызова, я бы хотел кое-что увидеть.

Ответ 1

a = collections.defaultdict(lambda: collections.defaultdict(list))

Ответ 2

Возможно, это решает вашу потребность в любом количестве "измерений" в вашем словаре:

a= collections.defaultdict(list)

единственное изменение в вашем коде:

a['x', 'y'].append('z')

Конечно, это решение, которое вы хотите, зависит от двух условий:

  • нужно ли вам легко получить доступ ко всем спискам, например. 'x' в "первом измерении"
  • ли вы застряли в том, как Perl волшебным образом нравится вам больше:)

Если любое из этих двух условий истинно, мое решение не поможет вам.

Ответ 3

Поскольку мы не знаем заранее, нужен ли нам словарь или список, вы не можете объединить автовивитацию со списками. Если, Отклик от ответа Nosklo из связанного вопроса, вы добавляете список "функций" в основной словарь. В основном, предполагая "сортировку" заказа для ключей и всегда используя его со списком методов. Я сделал это в качестве примера:

class AutoVivification(dict):
    """Implementation of perl autovivification feature. Has features from both dicts and lists,
    dynamically generates new subitems as needed, and allows for working (somewhat) as a basic type.
    """
    def __getitem__(self, item):
        if isinstance(item, slice):
            d = AutoVivification()
            items = sorted(self.iteritems(), reverse=True)
            k,v = items.pop(0)
            while 1:
                if (item.start < k < item.stop):
                    d[k] = v
                elif k > item.stop:
                    break
                if item.step:
                    for x in range(item.step):
                        k,v = items.pop(0)
                else:
                    k,v = items.pop(0)
            return d
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

    def __add__(self, other):
        """If attempting addition, use our length as the 'value'."""
        return len(self) + other

    def __radd__(self, other):
        """If the other type does not support addition with us, this addition method will be tried."""
        return len(self) + other

    def append(self, item):
        """Add the item to the dict, giving it a higher integer key than any currently in use."""
        largestKey = sorted(self.keys())[-1]
        if isinstance(largestKey, str):
            self.__setitem__(0, item)
        elif isinstance(largestKey, int):
            self.__setitem__(largestKey+1, item)

    def count(self, item):
        """Count the number of keys with the specified item."""
        return sum([1 for x in self.items() if x == item])

    def __eq__(self, other):
        """od.__eq__(y) <==> od==y. Comparison to another AV is order-sensitive
        while comparison to a regular mapping is order-insensitive. """
        if isinstance(other, AutoVivification):
            return len(self)==len(other) and self.items() == other.items()
        return dict.__eq__(self, other)

    def __ne__(self, other):
        """od.__ne__(y) <==> od!=y"""
        return not self == other

Это следует за основной функцией автообвинения, динамически генерирующей себя для ключей dud. Однако он также реализует некоторые из методов перечисленных здесь. Это позволяет ему действовать как вещь квази-списка/dict.

Для остальных функций списка добавьте перечисленные методы. Я рассматриваю его как словарь со списком методов. Если вы вызываете метод list, то он делает предположение о порядке сохраненных элементов, а именно, что строки сортируются ниже целых чисел и что ключи всегда находятся в порядке сортировки.

Он также поддерживает добавление в качестве примера этих методов. Это происходит из моего собственного варианта использования. Мне нужно было добавлять элементы из словаря AutoVivified, но если он не существует, создается и возвращается новый объект AutoVivification. Они не имеют целочисленного значения и поэтому вы не можете этого сделать:

rp = AutoVivification()
rp['a']['b'] = 3
rp['a']['b'] + rp['q']

Это побеждает цель, так как я не знаю, будет ли что-то там, но я все равно хочу по умолчанию. Поэтому я добавил методы __add__ и __radd__. Они используют length базового словаря в качестве значения integer, поэтому вновь созданный объект AV имеет значение нуля для добавления. Если у ключа есть что-то помимо AV-объекта в нем, то мы получим этот метод добавления вещей, если он будет реализован.

Ответ 4

Просто расширяя на Ignacio ответ, чтобы представить некоторые дополнительные опции, которые позволяют вам явно запрашивать более волшебное поведение из словарей Python. Ремонтопригодность кода, написанного таким образом, остается сомнительной, но я хотел бы четко сформулировать, что речь идет о "Является ли такая структура данных устойчивой?" (Я сомневаюсь) не "Может ли Python вести себя так?" (это, безусловно, может).

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

>>> from collections import defaultdict
>>> def autodict(): return defaultdict(autodict)
...
>>> a = autodict()
>>> a[1][2][3] = []
>>> type(a[1])
<class 'collections.defaultdict'>
>>> type(a[1][2])
<class 'collections.defaultdict'>
>>> type(a[1][2][3])
<class 'list'>
>>> a[1][2][3]
[]

Тем не менее, это вводит "проблему", которую вы должны явно задать, прежде чем вы сможете добавить к ней. Ответ Python на этот вопрос лежит в методе setdefault, который на самом деле был длиннее collections.defaultdict:

>>> a.setdefault(3, []).append(10)
>>> a.setdefault(3, []).append(11)
>>> a[3]
[10, 11]
>>> a[2].setdefault(3, []).append(12)
>>> a[2].setdefault(3, []).append(13)
>>> a[2][3]
[12, 13]
>>> a[1][2].setdefault(3, []).append(14)
>>> a[1][2].setdefault(3, []).append(15)
>>> a[1][2][3]
[14, 15]

Все collections.defaultdict действительно делает общий случай, когда вы всегда передаете тот же второй параметр в dict.setdefault гораздо проще в использовании. Для более сложных случаев, таких как этот, вы все равно можете использовать dict.setdefault непосредственно для аспектов, которые collections.defaultdict не может обрабатывать.