Как мне сделать расширенную автонавификацию хеша Python? - PullRequest
10 голосов
/ 13 марта 2010

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

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

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

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

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

Ответы [ 4 ]

16 голосов
/ 13 марта 2010
a = collections.defaultdict(lambda: collections.defaultdict(list))
2 голосов
/ 09 мая 2012

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

class AutoVivification(dict):
    """Implementation of perl's 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. Тем не менее, он также реализует некоторые из методов , перечисленных здесь . Это позволяет ему действовать как квази-список / диктат.

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

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

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

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

2 голосов
/ 09 апреля 2010

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

a= collections.defaultdict(list)

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

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

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

  1. нужен ли вам легкий доступ ко всем спискам, например, с помощью «х» в «первом измерении»
  2. застрял ли ты на том, что Perl волшебным образом доставляет тебе больше удовольствия:)

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

1 голос
/ 28 августа 2012

Просто подробно остановимся на ответе Игнасио, чтобы представить некоторые дополнительные опции, которые позволяют вам явно запрашивать более магическое поведение в словарях 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 не может обработать.

...