Ответ 1
Использование:
d = defaultdict(lambda: defaultdict(int))
Это создаст новый defaultdict(int)
при каждом новом доступе в d
.
Благодаря великим людям на SO, я обнаружил возможности, предлагаемые collections.defaultdict
, особенно в читабельности и скорости. Я использовал их с успехом.
Теперь я хотел бы реализовать три уровня словарей, а два верхних - defaultdict
, а самый низкий - int
. Я не нашел подходящего способа сделать это. Вот моя попытка:
from collections import defaultdict
d = defaultdict(defaultdict)
a = [("key1", {"a1":22, "a2":33}),
("key2", {"a1":32, "a2":55}),
("key3", {"a1":43, "a2":44})]
for i in a:
d[i[0]] = i[1]
Теперь это работает, но следующее, которое является желаемым поведением, не делает:
d["key4"]["a1"] + 1
Я подозреваю, что должен был объявить где-то, что второй уровень defaultdict
имеет тип int
, но я не нашел, где и как это сделать.
Причина, по которой я использую defaultdict
, заключается в том, чтобы избежать необходимости инициализировать словарь для каждого нового ключа.
Любое более элегантное предложение?
Спасибо pythoneers!
Использование:
d = defaultdict(lambda: defaultdict(int))
Это создаст новый defaultdict(int)
при каждом новом доступе в d
.
Другой способ сделать разборчивый, вложенный defaultdict - использовать частичный объект вместо лямбда:
from functools import partial
...
d = defaultdict(partial(defaultdict, int))
Это будет работать, потому что класс defaultdict доступен на глобальном уровне на уровне модуля:
"Вы не можете разгадать частичный объект, если функция [или в этом case, class] it wraps доступен по всему миру... под его __name__ (в пределах его __module__)" - Обертывание частичных функций
Посмотрите на nosklo answer здесь для более общего решения.
class AutoVivification(dict): """Implementation of perl autovivification feature.""" def __getitem__(self, item): try: return dict.__getitem__(self, item) except KeyError: value = self[item] = type(self)() return value
Тестирование:
a = AutoVivification() a[1][2][3] = 4 a[1][3][3] = 5 a[1][2]['test'] = 6 print a
Вывод:
{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}
В соответствии с запросом @rschwieb для D['key'] += 1
, мы можем перейти на предыдущий путем переопределения добавления, определяя метод __add__
, чтобы сделать это более похожим на collections.Counter()
Первый __missing__
будет вызываться для создания нового пустого значения, которое будет передано в __add__
. Мы тестируем значение, считая пустые значения False
.
Подробнее о переопределении см. эмуляция числовых типов.
from numbers import Number
class autovivify(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
def __add__(self, x):
""" override addition for numeric types when self is empty """
if not self and isinstance(x, Number):
return x
raise ValueError
def __sub__(self, x):
if not self and isinstance(x, Number):
return -1 * x
raise ValueError
Примеры:
>>> import autovivify
>>> a = autovivify.autovivify()
>>> a
{}
>>> a[2]
{}
>>> a
{2: {}}
>>> a[4] += 1
>>> a[5][3][2] -= 1
>>> a
{2: {}, 4: 1, 5: {3: {2: -1}}}
Вместо проверки аргумента есть Number (очень не-python, amirite!), мы могли бы просто указать значение по умолчанию 0 и затем выполнить операцию:
class av2(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
def __add__(self, x):
""" override addition when self is empty """
if not self:
return 0 + x
raise ValueError
def __sub__(self, x):
""" override subtraction when self is empty """
if not self:
return 0 - x
raise ValueError
Поздно к партии, но для произвольной глубины я просто обнаружил, что делаю что-то вроде этого:
from collections import defaultdict
class DeepDict(defaultdict):
def __call__(self):
return DeepDict(self.default_factory)
Трюк здесь в основном заключается в том, чтобы сделать экземпляр DeepDict
сам по себе действительной фабрикой для построения отсутствующих значений. Теперь мы можем делать такие вещи, как
dd = DeepDict(DeepDict(list))
dd[1][2].extend([3,4])
sum(dd[1][2]) # 7
ddd = DeepDict(DeepDict(DeepDict(list)))
ddd[1][2][3].extend([4,5])
sum(ddd[1][2][3]) # 9
def _sub_getitem(self, k):
try:
# sub.__class__.__bases__[0]
real_val = self.__class__.mro()[-2].__getitem__(self, k)
val = '' if real_val is None else real_val
except Exception:
val = ''
real_val = None
# isinstance(Avoid,dict)也是true,会一直递归死
if type(val) in (dict, list, str, tuple):
val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
# 重新赋值当前字典键为返回值,当对其赋值时可回溯
if all([real_val is not None, isinstance(self, (dict, list)), type(k) is not slice]):
self[k] = val
return val
def _sub_pop(self, k=-1):
try:
val = self.__class__.mro()[-2].pop(self, k)
val = '' if val is None else val
except Exception:
val = ''
if type(val) in (dict, list, str, tuple):
val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
return val
class DefaultDict(dict):
def __getitem__(self, k):
return _sub_getitem(self, k)
def pop(self, k):
return _sub_pop(self, k)
In[8]: d=DefaultDict()
In[9]: d['a']['b']['c']['d']
Out[9]: ''
In[10]: d['a']="ggggggg"
In[11]: d['a']
Out[11]: 'ggggggg'
In[12]: d['a']['pp']
Out[12]: ''
Нет ошибок снова. Неважно, сколько уровней вложено. не появляется также ошибка
дд = DefaultDict ({ "1": 333333})
Я также хотел бы предложить реализацию в стиле ООП, которая поддерживает вложение на произвольном уровне, а также правильно отформатированный repr
.
class NestedDefaultDict(defaultdict):
def __init__(self):
super(NestedDefaultDict, self).__init__(NestedDefaultDict)
def __repr__(self):
return repr(dict(self))
Использование:
my_dict = NestedDefaultDict()
my_dict['a']['b'] = 1
my_dict['a']['c']['d'] = 2
my_dict['b']
print(my_dict) # {'a': {'b': 1, 'c': {'d': 2}}, 'b': {}}