Как увеличить значение (в 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 года):
На этот вопрос до сих пор нет ответа. Понятно, что есть варианты "как делать то, что я хочу", но вопрос прост: есть "значение", мы передаем его функции, функция изменяет ее. Похоже, что это невозможно. Просто значение, без каких-либо "дополнительных ключей" и т. Д. Если это так, можем ли мы сделать ответ более общим?
Заметки:
- Что такое defaultdict из defaultdicts - ТАК.
- Этот вопрос не о "хранении целых чисел в defaultdict", поэтому я не ищу иерархию defaultdicts с типом int на листьях.
- Предположим, что тип (
int
в примерах) известен заранее / может быть даже параметризован (включая способность выполнять+=
оператор) - вопрос в том, как разыменовать объект, передать его для модификации и сохранить обратно в контексте defaultdict defaultdicts. - Ответ на этот вопрос связан с изменчивостью? Смотрите пример ниже:
Пример:
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
, У вас есть следующие варианты:
- Вложенные
defaultdict
изdefaultdict
объекты на неопределенный срок; defaultdict
изint
предметы, которые скорее всего не подойдут вам;defaultdict
изdefaultdict
до определенного уровня сint
определено для последнего уровня, напримерd = defaultdict(lambda: defaultdict(int))
для одиночного вложения;- Аналогично (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})})