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

Python: легко получить доступ к глубоко вложенному dict (get и set)

Я создаю код Python для чтения и управления глубоко вложенными диктофонами (в конечном счете для взаимодействия с службами JSON, однако было бы здорово иметь другие цели). Я ищу способ легко прочитать/установить/обновить значения в пределах dict, не требуя большого количества кода.

@see также Python: рекурсивно доступ к dict через атрибуты, а также доступ к индексу? - Решение Curt Hagenlocher "DotDictify" довольно красноречиво. Мне также нравится, что Бен Алман представляет для JavaScript в http://benalman.com/projects/jquery-getobject-plugin/ Было бы здорово как-то объединить два.

Исходя из примеров Curt Hagenlocher и Ben Alman, было бы замечательно, если бы на Python появилась такая возможность:

>>> my_obj = DotDictify()
>>> my_obj.a.b.c = {'d':1, 'e':2}
>>> print my_obj
{'a': {'b': {'c': {'d': 1, 'e': 2}}}}
>>> print my_obj.a.b.c.d
1
>>> print my_obj.a.b.c.x
None
>>> print my_obj.a.b.c.d.x
None
>>> print my_obj.a.b.c.d.x.y.z
None

Любая идея, если это возможно, и если да, то как изменить модификацию решения DotDictify?

В качестве альтернативы, метод get может быть выполнен для принятия точечной нотации (и добавлен дополнительный метод набора), однако обозначение объекта является более чистым.

>>> my_obj = DotDictify()
>>> my_obj.set('a.b.c', {'d':1, 'e':2})
>>> print my_obj
{'a': {'b': {'c': {'d': 1, 'e': 2}}}}
>>> print my_obj.get('a.b.c.d')
1
>>> print my_obj.get('a.b.c.x')
None
>>> print my_obj.get('a.b.c.d.x')
None
>>> print my_obj.get('a.b.c.d.x.y.z')
None

Этот тип взаимодействия был бы полезен для работы с глубоко вложенными диктофонами. Кто-нибудь знает другую стратегию (или образец фрагмента/библиотеки кода), чтобы попробовать?

4b9b3361

Ответ 1

Дерево атрибутов

Проблема с вашей первой спецификацией заключается в том, что Python не может сказать в __getitem__, если в my_obj.a.b.c.d вы продолжите дальше по несуществующему дереву, и в этом случае ему нужно вернуть объект с помощью __getitem__, чтобы вы не выбрали AttributeError для вас или хотите получить значение, и в этом случае ему нужно вернуть None.

Я бы сказал, что в каждом случае, у вас есть выше, вы должны ожидать, чтобы он выбрал KeyError вместо возврата None. Причина в том, что вы не можете определить, означает ли None "нет ключа" или "кто-то действительно хранит None в этом месте". Для этого вам нужно всего лишь взять dotdictify, удалить marker и заменить __getitem__ на:

def __getitem__(self, key):
    return self[key]

Потому что вы действительно хотите dict с __getattr__ и __setattr__.

Возможно, существует способ полностью удалить __getitem__ и сказать что-то вроде __getattr__ = dict.__getitem__, но я думаю, что это может быть чрезмерная оптимизация и будет проблемой, если вы позже решите, что хотите __getitem__ создать дерево как это происходит, например, dotdictify, и в этом случае вы изменили бы его на:

def __getitem__(self, key):
    if key not in self:
        dict.__setitem__(self, key, dotdictify())
    return dict.__getitem__(self, key)

Мне не нравится бизнес marker в оригинале dotdictify.

Поддержка путей

Вторая спецификация (переопределить get() и set()) состоит в том, что нормальный dict имеет get(), который работает иначе, чем то, что вы описываете, и даже не имеет set (хотя он имеет setdefault(), которая является обратной операцией get()). Люди ожидают, что get примет два параметра, второй - по умолчанию, если ключ не найден.

Если вы хотите расширить __getitem__ и __setitem__ для обработки меток с метками, вам необходимо изменить doctictify на:

class dotdictify(dict):
    def __init__(self, value=None):
        if value is None:
            pass
        elif isinstance(value, dict):
            for key in value:
                self.__setitem__(key, value[key])
        else:
            raise TypeError, 'expected dict'

    def __setitem__(self, key, value):
        if '.' in key:
            myKey, restOfKey = key.split('.', 1)
            target = self.setdefault(myKey, dotdictify())
            if not isinstance(target, dotdictify):
                raise KeyError, 'cannot set "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target))
            target[restOfKey] = value
        else:
            if isinstance(value, dict) and not isinstance(value, dotdictify):
                value = dotdictify(value)
            dict.__setitem__(self, key, value)

    def __getitem__(self, key):
        if '.' not in key:
            return dict.__getitem__(self, key)
        myKey, restOfKey = key.split('.', 1)
        target = dict.__getitem__(self, myKey)
        if not isinstance(target, dotdictify):
            raise KeyError, 'cannot get "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target))
        return target[restOfKey]

    def __contains__(self, key):
        if '.' not in key:
            return dict.__contains__(self, key)
        myKey, restOfKey = key.split('.', 1)
        target = dict.__getitem__(self, myKey)
        if not isinstance(target, dotdictify):
            return False
        return restOfKey in target

    def setdefault(self, key, default):
        if key not in self:
            self[key] = default
        return self[key]

    __setattr__ = __setitem__
    __getattr__ = __getitem__

Тестовый код:

>>> life = dotdictify({'bigBang': {'stars': {'planets': {}}}})
>>> life.bigBang.stars.planets
{}
>>> life.bigBang.stars.planets.earth = { 'singleCellLife' : {} }
>>> life.bigBang.stars.planets
{'earth': {'singleCellLife': {}}}
>>> life['bigBang.stars.planets.mars.landers.vikings'] = 2
>>> life.bigBang.stars.planets.mars.landers.vikings
2
>>> 'landers.vikings' in life.bigBang.stars.planets.mars
True
>>> life.get('bigBang.stars.planets.mars.landers.spirit', True)
True
>>> life.setdefault('bigBang.stars.planets.mars.landers.opportunity', True)
True
>>> 'landers.opportunity' in life.bigBang.stars.planets.mars
True
>>> life.bigBang.stars.planets.mars
{'landers': {'opportunity': True, 'vikings': 2}}

Ответ 2

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

class Trie:
    """
    A Trie is like a dictionary in that it maps keys to values.
    However, because of the way keys are stored, it allows
    look up based on the longest prefix that matches.

    """

    def __init__(self):
        # Every node consists of a list with two position.  In
        # the first one,there is the value while on the second
        # one a dictionary which leads to the rest of the nodes.
        self.root = [0, {}]


    def insert(self, key):
        """
        Add the given value for the given key.

        >>> a = Trie()
        >>> a.insert('kalo')
        >>> print(a)
        [0, {'k': [1, {'a': [1, {'l': [1, {'o': [1, {}]}]}]}]}]
        >>> a.insert('kalo')
        >>> print(a)
        [0, {'k': [2, {'a': [2, {'l': [2, {'o': [2, {}]}]}]}]}]
        >>> b = Trie()
        >>> b.insert('heh')
        >>> b.insert('ha')
        >>> print(b)
        [0, {'h': [2, {'a': [1, {}], 'e': [1, {'h': [1, {}]}]}]}]

        """

        # find the node to append the new value.
        curr_node = self.root
        for k in key:
            curr_node = curr_node[1].setdefault(k, [0, {}])
            curr_node[0] += 1


    def find(self, key):
        """
        Return the value for the given key or None if key not
        found.

        >>> a = Trie()
        >>> a.insert('ha')
        >>> a.insert('ha')
        >>> a.insert('he')
        >>> a.insert('ho')
        >>> print(a.find('h'))
        4
        >>> print(a.find('ha'))
        2
        >>> print(a.find('he'))
        1

        """

        curr_node = self.root
        for k in key:
            try:
                curr_node = curr_node[1][k]
            except KeyError:
                return 0
        return curr_node[0]

    def __str__(self):
        return str(self.root)

    def __getitem__(self, key):
        curr_node = self.root
        for k in key:
            try:
                curr_node = curr_node[1][k]
            except KeyError:
                yield None
        for k in curr_node[1]:
            yield k, curr_node[1][k][0]

if __name__ == '__main__':
    a = Trie()
    a.insert('kalo')
    a.insert('kala')
    a.insert('kal')
    a.insert('kata')
    print(a.find('kala'))
    for b in a['ka']:
        print(b)
    print(a)

Ответ 3

Другим гуглерам: теперь у нас addict:

pip install addict

и

mapping.a.b.c.d.e = 2
mapping
{'a': {'b': {'c': {'d': {'e': 2}}}}}

Я использовал его широко.

Чтобы работать с пунктирными путями, я нашел dotted:

obj = DottedDict({'hello': {'world': {'wide': 'web'}}})
obj['hello.world.wide'] == 'web'  # true