2016-10-01 4 views
0

Я создаю класс, который наследует OrderedDict, в котором каждый ключ возвращает список. Я хочу перегрузить setitem, так что если ключ не существует, новые назначения сразу же помещают значение в список, иначе новое значение добавляется к списку. Далее, кажется, работает:Python OrderedDict __setitem__ overloading

from collections import OrderedDict 

class ListDict(OrderedDict): 
    def __init__(self): 
     super(ListDict, self).__init__() 

    def __setitem__(self, key, value): 
     if key in self: 
      self[key].append(value) 
     else: 
      super(ListDict, self).__setitem__(key, [value]) 

thedict = ListDict() 

thedict['a'] = 'first item' 
thedict['b'] = 'another first item' 
thedict['a'] = 'a second item?' 

print thedict 

который печатает:

$ python inheritex.py 
ListDict([('a', ['first item', 'a second item?']), ('b', ['another first item'])]) 

Вместо добавления с оператором присваивания «=», я предпочел бы новые элементы добавляются с «+ =», или даже что-то вроде:

ListDict['key'] = ListDict['key'] + 'value' 

Как можно перегружать это? Кроме того, возможно, что обезьяна-патч добавляет функцию add, это даже Pythonic/readable означает изменить поведение класса, или это приемлемо, потому что унаследованная функция (OrderedDict) не затронута?

+0

FWIW,' если ключ в self' является _much_ лучше, чем 'если ключ в self.keys()' :-) – mgilson

+0

@mgilson: Оооф вы правы. >. <Я все еще вижу код наследования с 'self' в нем и получаю woozy и disoriented ... – ultraturtle0

ответ

2

Вы уже можете использовать += на существующих ключей:

>>> from collections import OrderedDict 
>>> thedict = OrderedDict() 
>>> thedict['a'] = [] # set initial list 
>>> thedict['a'] += ['foo'] 
>>> thedict['a'] += ['bar'] 
>>> thedict 
OrderedDict([('a', ['foo', 'bar'])]) 

Обратите внимание, что += в списке является в основном то же самое, как list.extend(), поэтому вам нужно добавить списки.

Если вы хотите, чтобы работать для ключей, которые еще не существуют, реализовать __missing__ method, а не __setitem__:

class ListDict(OrderedDict): 
    def __missing__(self, key): 
     self[key] = [] 
     return self[key] 

Когда ключ отсутствует во время dict[key] поиска, __missing__ называется, и это возвращаемое значение возвращается вместо повышения KeyError.

Теперь оба + и += работы над недостающими ключами тоже:

>>> thedict = ListDict() 
>>> thedict['a'] += ['foo', 'bar'] 
>>> thedict['b'] = thedict['b'] + ['spam', 'ham'] 
>>> thedict 
ListDict([('a', ['foo', 'bar']), ('b', ['spam', 'ham'])]) 

Если вы конкатенации должны работать без добавления списков, вы можете создать пользовательский список подкласс также:

class ConcatList(list): 
    def __add__(self, value): 
     return type(self)(super(ContactList, self).__add__([value])) 
    def __iadd__(self, value): 
     self.append(value) 
     return self 

затем использовать это введите __missing__ (и преобразуйте любые новые списки, установленные непосредственно в __setitem__):

class ListDict(OrderedDict): 
    def __missing__(self, key): 
     self[key] = ConcatList() 
     return self[key] 
    def __setitem__(self, key, value): 
     if not isinstance(key, ConcatList): 
      value = ConcatList([value]) 
     super(ListDict, self).__setitem__(key, value) 

, после чего вы можете отказаться от скобки:

>>> thedict = ListDict() 
>>> thedict['a'] += 'foo' 
>>> thedict['b'] = thedict['b'] + 'bar' 
>>> thedict 
ListDict([('a', ['foo']), ('b', ['bar'])]) 
+0

Я подозреваю, вы также можете использовать множественное наследование для объединения' OrderedDict' с 'defaultdict' (вам просто нужно перегрузить' __init__ 'передать' list' в качестве заводской функции классу 'defaultdict'). – Blckknght

+0

@Blckknght: возможно, но гораздо проще просто пойти с пользовательским '__missing__', чем выяснять, как сохранить счастливые инициализаторы' defaultdict' и 'OrderedDict'. –

+0

Да, похоже, на самом деле это не работает из-за какого-то конфликта (по крайней мере, в последних версиях Python. Я изучал его для своего собственного ответа, когда увидел, что ваша часть покрывает большую часть того же материала. – Blckknght

0

Я продлевал Martijn'sListDict, так что вы можете обеспечить default_factory и имеют похожие на collections.defaultdict «s функции + ключа заказа:

class OrderedDefaultDict(OrderedDict): 
    def __init__(self, default_factory=None): 
     self.default_factory = default_factory 

    def __missing__(self, key): 
     if self.default_factory is None: 
      raise KeyError 
     self[key] = self.default_factory() 
     return self[key] 

list_dict = OrderedDefaultDict(list) 
list_dict['first'].append(1) 
list_dict['second'].append(2) 
list_dict['third'].append(3) 
assert list(list_dict.keys()) == ['first', 'second', 'third'] 
assert list(list_dict.values()) == [[1], [2], [3]] 

list_dict.move_to_end('first') 
assert list(list_dict.keys()) == ['second', 'third', 'first'] 
Смежные вопросы