Нарезка ордера Python
В моем коде мне часто нужно взять подмножество диапазона ключей + значений из пакета Python OrderedDict
(из collections
), Нарезка не работает (выбрасывает TypeError: unhashable type
), а альтернатива, итерация, громоздка:
from collections import OrderedDict
o = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
# want to do:
# x = o[1:3]
# need to do:
x = OrderedDict()
for idx, key in enumerate(o):
if 1 <= idx < 3:
x[key] = o[key]
Есть ли лучший способ сделать это?
Ответы
Ответ 1
Упорядоченный dict в стандартной библиотеке, не обеспечивает эту функциональность. Несмотря на то, что библиотеки существовали в течение нескольких лет до коллекций. OderedDict, которые имеют эту функциональность (и предоставляют по существу надмножество OrderedDict): voidspace odict и ruamel.ordereddict (я являюсь автором последнего пакета, который является повторной реализацией odict в C):
from odict import OrderedDict as odict
p = odict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print p[1:3]
В ruamel.ordereddict вы можете отменить упорядоченное требование ввода (AFAIK вы не можете запросить производную от dict, если его ключи упорядочены (было бы хорошим дополнением к ruamel.ordereddict для распознавания коллекции .OrderedDicts):
from ruamel.ordereddict import ordereddict
q = ordereddict(o, relax=True)
print q[1:3]
r = odict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print r[1:3]
Если вы хотите (или должны) оставаться в стандартной библиотеке, вы можете поднять collections.OrderedDict
__getitem__
:
class SlicableOrderedDict(OrderedDict):
def __getitem__(self, k):
if not isinstance(k, slice):
return OrderedDict.__getitem__(self, k)
x = SlicableOrderedDict()
for idx, key in enumerate(self.keys()):
if k.start <= idx < k.stop:
x[key] = self[key]
return x
s = SlicableOrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print s[1:3]
конечно, вы могли бы использовать более ранние версии Martijn или Jimmy, чтобы получить реальный фрагмент, который нуждается в возврате:
from itertools import islice
class SlicableOrderedDict(OrderedDict):
def __getitem__(self, k):
if not isinstance(k, slice):
return OrderedDict.__getitem__(self, k)
return SlicableOrderedDict(islice(self.viewitems(), k.start, k.stop))
t = SlicableOrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print t[1:3]
или если вы просто хотите поднять все существующие OrderedDict
без подкласса:
def get_item(self, k):
if not isinstance(k, slice):
return OrderedDict._old__getitem__(self, k)
return OrderedDict(islice(self.viewitems(), k.start, k.stop))
OrderedDict._old__getitem__ = OrderedDict.__getitem__
OrderedDict.__getitem__ = get_item
u = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print u[1:3]
Ответ 2
Вы можете использовать функцию itertools.islice
, которая принимает итерацию и выводит первые элементы stop
. Это полезно, так как iterables не поддерживают общий метод нарезки, и вам не нужно будет создавать весь список items
из OrderedDict.
from collections import OrderedDict
from itertools import islice
o = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
sliced = islice(o.iteritems(), 3) # o.iteritems() is o.items() in Python 3
sliced_o = OrderedDict(sliced)
Ответ 3
В Python 2 вы можете нарезать клавиши:
x.keys()[1:3]
и для поддержки Python 2 и Python 3 вы сначала конвертируете в список:
list(k)[1:3]
Реализация Python 2 OrderedDict.keys()
делает именно это.
В обоих случаях вам предоставляется список ключей в правильном порядке. Если сначала создать полный список, вы можете использовать itertools.islice()
и преобразовать его, который он производит в список:
from itertools import islice
list(islice(x, 1, 3))
Все вышеперечисленное также может быть применено к предметам; используйте dict.viewitems()
в Python 2, чтобы получить такое же поведение итерации, что и Python 3 dict.items()
. Вы можете передать объект islice()
прямо в другой OrderedDict()
в этом случае:
OrderedDict(islice(x.items(), 1, 3)) # x.viewitems() in Python 2
Ответ 4
Я хотел нарезать ключом, так как я не знал индекс заранее:
o = OrderedDict(zip(list('abcdefghijklmnopqrstuvwxyz'),range(1,27)))
stop = o.keys().index('e') # -> 4
OrderedDict(islice(o.items(),stop)) # -> OrderedDict([('a', 1), ('b', 2), ('c', 3)])
или отрезать от start
до stop
:
start = o.keys().index('c') # -> 2
stop = o.keys().index('e') # -> 4
OrderedDict(islice(o.iteritems(),start,stop)) # -> OrderedDict([('c', 3), ('d', 4)])
Ответ 5
def slice_odict(odict, start=None, end=None):
return OrderedDict([
(k,v) for (k,v) in odict.items()
if k in list(odict.keys())[start:end]
])
Это позволяет:
>>> x = OrderedDict([('a',1), ('b',2), ('c',3), ('d',4)])
>>> slice_odict(x, start=-1)
OrderedDict([('d', 4)])
>>> slice_odict(x, end=-1)
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
>>> slice_odict(x, start=1, end=3)
OrderedDict([('b', 2), ('c', 3)])