Как увеличить значение (в defaultdict из defaultdicts)?

Как увеличить d['a']['b']['c'][1][2][3] если d является defaultdict из defaultdict без дублирования кода?

from collections import defaultdict
nested_dict_type = lambda: defaultdict(nested_dict_type)
nested_dict = nested_dict_type()

# incrementation
if type(nested_dict['a']['b']['c']['d'][1][2][3][4][5][6]) != int:
    nested_dict['a']['b']['c']['d'][1][2][3][4][5][6] = 0
nested_dict['a']['b']['c']['d'][1][2][3][4][5][6] += 1  # ok, now it contains 1

Здесь мы видим, что мы дублировали (в коде) цепочку ключей 3 раза.

Вопрос: можно ли написать функцию inc это займет nested_dict['a']['b']...[6] и делать ту же работу, что и выше? Так:

def inc(x):
    if type(x) != int:
        x = 0
    x += 1
inc(nested_dict['a']['b']['c']['d'][1][2][3][4][5][6])  # ok, now it contains 1

Обновление (20 августа 2018 года):

На этот вопрос до сих пор нет ответа. Понятно, что есть варианты "как делать то, что я хочу", но вопрос прост: есть "значение", мы передаем его функции, функция изменяет ее. Похоже, что это невозможно. Просто значение, без каких-либо "дополнительных ключей" и т. Д. Если это так, можем ли мы сделать ответ более общим?

Заметки:

  1. Что такое defaultdict из defaultdicts - ТАК.
  2. Этот вопрос не о "хранении целых чисел в defaultdict", поэтому я не ищу иерархию defaultdicts с типом int на листьях.
  3. Предположим, что тип (int в примерах) известен заранее / может быть даже параметризован (включая способность выполнять += оператор) - вопрос в том, как разыменовать объект, передать его для модификации и сохранить обратно в контексте defaultdict defaultdicts.
  4. Ответ на этот вопрос связан с изменчивостью? Смотрите пример ниже:

Пример:

def inc(x):
    x += 1

d = {'a': int(0)}
inc(d['a'])  
# d['a'] == 0, immutable

d = {'a': Int(0)}
inc(d['a'])  
# d['a'] == 1, mutated

куда Int является:

class Int:
    def __init__(self, value):
        self.value = value
    def __add__(self, v):
        self.value += v
        return self
    def __repr__(self):
        return str(self.value)

2 ответа

Это не совсем изменчивость, больше о том, как присваивание выполняет привязку имени.

Когда вы делаете x = 0 в вашем inc Функция вы привязываете новый объект к имени xи любая связь между этим именем и предыдущим объектом, связанным с этим именем, теряется. Это не зависит от того, x изменчиво

Но с тех пор x является элементом изменяемого объекта, мы можем достичь того, что вы хотите, передав родительский изменяемый объект inc вместе с ключом, необходимым для доступа к нужному элементу.

from collections import defaultdict

nested_dict_type = lambda: defaultdict(nested_dict_type)
nested_dict = nested_dict_type()

# incrementation
def inc(ref, key):
    if not isinstance(ref[key], int):
        ref[key] = 0
    ref[key] += 1

d = nested_dict['a']['b']['c']['d'][1][2][3][4][5]
inc(d, 6)
print(d)

выход

defaultdict(<function <lambda> at 0xb730553c>, {6: 1})

Теперь мы не привязываем новый объект, мы просто мутируем существующий, поэтому оригинал d объект обновляется правильно.


Кстати, с этим глубоко вложенным диктатом немного больно работать. Может быть, есть лучший способ организовать ваши данные... Но в любом случае, одна вещь, которая может быть полезна при работе с глубокими вложениями, это использовать списки или кортежи ключей. Например,

q = nested_dict
keys = 'a', 'b', 'c', 'd', 1, 2, 3, 4, 5
for k in keys:
    q = q[k]

q теперь относится к nested_dict['a']['b']['c']['d'][1][2][3][4][5]

Вы не можете иметь несколько типов по умолчанию с defaultdict, У вас есть следующие варианты:

  1. Вложенные defaultdict из defaultdict объекты на неопределенный срок;
  2. defaultdict из int предметы, которые скорее всего не подойдут вам;
  3. defaultdict из defaultdict до определенного уровня с int определено для последнего уровня, например d = defaultdict(lambda: defaultdict(int)) для одиночного вложения;
  4. Аналогично (3), но для подсчета вы можете использовать collections.Counter вместо этого, т.е. d = defaultdict(Counter),

Я рекомендую третий или четвертый варианты, если вы всегда собираетесь опускаться до заданного уровня. Другими словами, скалярное значение будет предоставляться только на n- м уровне, где n является постоянным.

В противном случае, один из вариантов вручную состоит в том, чтобы иметь функцию, выполняющую типовое тестирование. В этом случае, try / except может быть хорошей альтернативой. Здесь мы также определяем рекурсивный алгоритм, который позволяет вам вводить список ключей, а не определять руководство __getitem__ звонки.

from collections import defaultdict
from functools import reduce
from operator import getitem

nested_dict_type = lambda: defaultdict(nested_dict_type)
d = nested_dict_type()

d[1][2] = 10

def inc(d_in, L):
    try:
        reduce(getitem, L[:-1], d_in)[L[-1]] += 1
    except TypeError:
        reduce(getitem, L[:-1], d_in)[L[-1]] = 1

inc(d, [1, 2])
inc(d, [1, 3])

print(d)

defaultdict({1: defaultdict({2: 11, 3: 1})})
Другие вопросы по тегам