Я хотел бы объединить OrderedDict()
и defaultdict()
из collections
в одном объекте, который должен быть упорядоченным, по умолчанию dict
.
Это возможно?
Как реализовать упорядоченный, по умолчанию, dict?
Ответ 1
Следующее (с использованием измененной версии этот рецепт) работает для меня:
from collections import OrderedDict, Callable
class DefaultOrderedDict(OrderedDict):
# Source: http://stackoverflow.com/a/6190500/562769
def __init__(self, default_factory=None, *a, **kw):
if (default_factory is not None and
not isinstance(default_factory, Callable)):
raise TypeError('first argument must be callable')
OrderedDict.__init__(self, *a, **kw)
self.default_factory = default_factory
def __getitem__(self, key):
try:
return OrderedDict.__getitem__(self, key)
except KeyError:
return self.__missing__(key)
def __missing__(self, key):
if self.default_factory is None:
raise KeyError(key)
self[key] = value = self.default_factory()
return value
def __reduce__(self):
if self.default_factory is None:
args = tuple()
else:
args = self.default_factory,
return type(self), args, None, None, self.items()
def copy(self):
return self.__copy__()
def __copy__(self):
return type(self)(self.default_factory, self)
def __deepcopy__(self, memo):
import copy
return type(self)(self.default_factory,
copy.deepcopy(self.items()))
def __repr__(self):
return 'OrderedDefaultDict(%s, %s)' % (self.default_factory,
OrderedDict.__repr__(self))
Ответ 2
Вот еще одна возможность, вдохновленная Raymond Hettinger super(), рассмотренный Super, протестирован на Python 2.7.X и 3.4.X:
from collections import OrderedDict, defaultdict
class OrderedDefaultDict(OrderedDict, defaultdict):
def __init__(self, default_factory=None, *args, **kwargs):
#in python3 you can omit the args to super
super(OrderedDefaultDict, self).__init__(*args, **kwargs)
self.default_factory = default_factory
Если вы проверите класс MRO (aka, help(OrderedDefaultDict)
), вы увидите следующее:
class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict)
| Method resolution order:
| OrderedDefaultDict
| collections.OrderedDict
| collections.defaultdict
| __builtin__.dict
| __builtin__.object
означает, что, когда инициализируется экземпляр OrderedDefaultDict
, он отсылает к init OrderedDict
, но этот, в свою очередь, вызовет методы defaultdict
перед вызовом __builtin__.dict
, что и есть то, что мы хотим.
Ответ 3
Вот еще одно решение подумать о том, что ваш случай использования прост, как мой, и вы не обязательно хотите добавить сложность DefaultOrderedDict
класса DefaultOrderedDict
в свой код.
from collections import OrderedDict
keys = ['a', 'b', 'c']
items = [(key, None) for key in keys]
od = OrderedDict(items)
(None
это мое желаемое значение по умолчанию.)
Обратите внимание, что это решение не будет работать, если одно из ваших требований - динамически вставлять новые ключи со значением по умолчанию. Компромисс простоты.
Обновление 3/13/17 - Я узнал об удобной функции для этого варианта использования. То же, что и выше, но вы можете опустить items =...
и просто:
od = OrderedDict.fromkeys(keys)
Выход:
OrderedDict([('a', None), ('b', None), ('c', None)])
И если ваши ключи являются одиночными символами, вы можете просто передать одну строку:
OrderedDict.fromkeys('abc')
Это имеет тот же результат, что и два приведенных выше примера.
Вы также можете передать значение по умолчанию в качестве второго аргумента в OrderedDict.fromkeys(...)
.
Ответ 4
Если вы хотите простое решение, которое не требует класса, вы можете просто использовать OrderedDict. setdefault (key, default=None)
OrderedDict. setdefault (key, default=None)
или OrderedDict. get (key, default=None)
OrderedDict. get (key, default=None)
. Если вы только получаете/устанавливаете из нескольких мест, скажем, в цикле, вы можете просто установить setdefault.
totals = collections.OrderedDict()
for i, x in some_generator():
totals[i] = totals.get(i, 0) + x
Это еще проще для списков с setdefault
:
agglomerate = collections.OrderedDict()
for i, x in some_generator():
agglomerate.setdefault(i, []).append(x)
Но если вы используете его более нескольких раз, возможно, лучше создать класс, как в других ответах.
Ответ 5
Более простая версия ответа @zeekay:
from collections import OrderedDict
class OrderedDefaultListDict(OrderedDict): #name according to default
def __missing__(self, key):
self[key] = value = [] #change to whatever default you want
return value
Ответ 6
Простое и элегантное решение, построенное на @NickBread. Имеет несколько иной API для установки фабрики, но хорошие значения по умолчанию всегда хороши.
class OrderedDefaultDict(OrderedDict):
factory = list
def __missing__(self, key):
self[key] = value = self.factory()
return value
Ответ 7
Еще один простой подход будет использовать словарь get
метод
>>> from collections import OrderedDict
>>> d = OrderedDict()
>>> d['key'] = d.get('key', 0) + 1
>>> d['key'] = d.get('key', 0) + 1
>>> d
OrderedDict([('key', 2)])
>>>
Ответ 8
Вдохновленный другими ответами на эту тему, вы можете использовать что-то вроде:
from collections import OrderedDict
class OrderedDefaultDict(OrderedDict):
def __missing__(self, key):
value = OrderedDefaultDict()
self[key] = value
return value
Я хотел бы знать, есть ли недостатки инициализации другого объекта того же класса в методе отсутствия.
Ответ 9
Я тестировал дефолт по умолчанию и обнаружил, что он также отсортирован! возможно, это было просто совпадение, но в любом случае вы можете использовать отсортированную функцию:
sorted(s.items())
я думаю, это проще