20

OrderedDictには、リスト(要素の順序付き)と辞書(インデックスではなくキー)の両方の機能があるため、キーを使用してスライスすることは当然のようです。どのようにPythonのOrderedDictで整数の代わりに文字列キーでスライスできますか?

>>> from collections import OrderedDict 
>>> cities = OrderedDict((('san francisco', 650), ('new york', 212), ('shanghai', 8621), ('barcelona', 42423))) 
>>> test['shanghai':] # I want all the cities from shanghai to the end of the list 
TypeError: unhashable type 

何これについて興味深いのは、それはあなたが原因OrderedDictionary.__getslice__実装されていないに参照してくださいねエラーではないということです。私は自分の__getslice__メソッドをOrderedDictに追加しようとしましたが、このTypeErrorの問題は継続しています。 Pythonは、スライスキーが整数であることを強制するために何らかの型チェックをしているようですが、__getslice__関数に渡される前に、どのようにunpythonicなのでしょう!

>>> class BetterOrderedDict(OrderedDict): 
     def __getslice__(self, start=None, end=None, step=1): 
      return 'potato' 

>>> test = BetterOrderedDict((('one', 1), ('two', 2), ('three', 3), ('four', 4))) 
>>> print test[1:4] 
'potato'       # ok this makes sense so far 

>>> test['one':'four'] 
TypeError: unhashable type   # WTF, strings are hashable! 

は、だから私の質問は、なぜカント私も私の __getslice__機能に達することから、スライス鍵を妨げている型チェックのどのような非整数スライスを、実装された、と私はCで、私の BetterOrderedDictを実装することによって、それを上書きすることができますバインディングで?

+0

あなたがキー「4」までのキー「1」からスライスを作成したいですか? –

+0

うん、彼らは命令を持っているので、それは大丈夫です。 –

+0

しかし...なぜですか?目的は何ですか? –

答えて

22

__getslice__は、スライシングを実装する方法として推奨されていません。代わりに、あなたは__getitem__sliceオブジェクト処理する必要があります:

from collections import OrderedDict 

class SlicableDict(OrderedDict): 
    def __getitem__(self, key): 
     if isinstance(key, slice): 
      return 'potato({},{},{})'.format(key.start, key.stop, key.step) 
     return super(SlicableDict, self).__getitem__(key) 

>>> s = SlicableDict(a=1, b=2, c=3) 
>>> s 
SlicableDict([('a', 1), ('c', 3), ('b', 2)]) 
>>> s['a'] 
1 
>>> s['a':'c'] 
'potato(a,c,None)' 

をそして、あなたは、あなたが道以下の3つのすべてのスライス操作を実装することができるよりも、ポテトよりも多くを必要とする場合:

def _key_slice_to_index_slice(items, key_slice): 
    try: 
     if key_slice.start is None: 
      start = None 
     else: 
      start = next(idx for idx, (key, value) in enumerate(items) 
         if key == key_slice.start) 
     if key_slice.stop is None: 
      stop = None 
     else: 
      stop = next(idx for idx, (key, value) in enumerate(items) 
         if key == key_slice.stop) 
    except StopIteration: 
     raise KeyError 
    return slice(start, stop, key_slice.step) 

class SlicableDict(OrderedDict): 
    def __getitem__(self, key): 
     if isinstance(key, slice): 
      items = self.items() 
      index_slice = _key_slice_to_index_slice(items, key) 
      return SlicableDict(items[index_slice]) 
     return super(SlicableDict, self).__getitem__(key) 

    def __setitem__(self, key, value): 
     if isinstance(key, slice): 
      items = self.items() 
      index_slice = _key_slice_to_index_slice(items, key) 
      items[index_slice] = value.items() 
      self.clear() 
      self.update(items) 
      return 
     return super(SlicableDict, self).__setitem__(key, value) 

    def __delitem__(self, key): 
     if isinstance(key, slice): 
      items = self.items() 
      index_slice = _key_slice_to_index_slice(items, key) 
      del items[index_slice] 
      self.clear() 
      self.update(items) 
      return 
     return super(SlicableDict, self).__delitem__(key) 
+0

素晴らしい、まさに私が探していたもの。 –

4

は、この(非常に醜い)の実装を試してみてください

class SliceOrdered(OrderedDict): 

    def __getitem__(self, key): 
     if isinstance(key, slice): 
      tmp = OrderedDict() 
      i_self = iter(self) 
      for k in i_self: 
       if key.start <= k <= key.stop: 
        tmp[k] = self[k] 
        if key.step is not None and key.step > 1: 
         for _ in range(key.step-1): 
          try: 
           next(i_self) 
          except StopIteration: 
           break 
      return tmp 
     else: 
      return super(SliceOrdered, self).__getitem__(key) 

DEMO(Python3.4)

>>> s = SliceOrdered([('a',2), ('b',2), ('c',3), ('d',4)]) 
>>> s['a':'c'] 
OrderedDict([('a', 2), ('b', 2), ('c', 3)]) 
>>> s['a':'d':2] 
OrderedDict([('a', 2), ('c', 3)]) 

N.B.これは、この例ではOrderedDictが注文されただけでなく、ソートされているためです。ソートされていない辞書では、スライス'a':'c'には'b'が含まれている必要はないので、私のif key.start <= k <= key.stopロジックはおそらく失敗します。次のコードはそれを尊重する必要があります。

class SliceOrdered(OrderedDict): 
    def __getitem__(self, key): 
     if not isinstance(key, slice): 
      return super(SliceOrdered,self).__getitem__(key) 
     tmp = OrderedDict() 
     step = key.step or 1 
     accumulating = False 
     i_self = iter(self) 
     for k in i_self: 
      if k == key.start: 
       accumulating = True 
      if accumulating: 
       tmp[k] = self[k] 
       for _ in range(step-1): 
        next(i_self) 
      if k == key.stop: 
       accumulating = False 
       break 
     return tmp 
+0

ありがとう、私は知りませんでした\ __getitem__がgetsliceの代わりに使われていましたが、これは今や非常に意味があります。 –

12

これは期待しているスライシング機能の実際の実装です。

OrderedDictは内部的に二重リンクリストの形式でキーの順序を維持します。 Quoting the actual comment from Python 2.7.9

# The internal self.__map dict maps keys to links in a doubly linked list. 
# The circular doubly linked list starts and ends with a sentinel element. 
# The sentinel element never gets deleted (this simplifies the algorithm). 
# Each link is stored as a list of length three: [PREV, NEXT, KEY]. 

は今、辞書をスライスして、我々は実際にname mangling mechanismによって保護されたプライベート変数であり、二重リンクリスト、__rootを、反復処理する必要があります。

注:これには、OrderedDictの内部データ構造を使用するためのハッキーネームが含まれています。

from collections import OrderedDict 

class SlicableDict(OrderedDict): 
    def __getitem__(self, key): 
     if isinstance(key, slice): 
      # Unmangle `__root` to access the doubly linked list 
      root = getattr(self, "_OrderedDict__root") 
      # By default, make `start` as the first element, `end` as the last 
      start, end = root[1][2], root[0][2] 
      start = key.start or start 
      end = key.stop or end 
      step = key.step or 1 
      curr, result, begun, counter = root[1], [], False, 0 

      # Begin iterating 
      curr, result, begun = root[1], [], False 
      while curr is not root: 
       # If the end value is reached, `break` and `return` 
       if curr[2] == end: 
        break 
       # If starting value is matched, start appending to `result` 
       if curr[2] == start: 
        begun = True 
       if begun: 
        if counter % step == 0: 
         result.append((curr[2], self[curr[2]])) 
        counter += 1 

       # Make the `curr` point to the next element 
       curr = curr[1] 

      return result 

     return super(SlicableDict, self).__getitem__(key) 

いくつかのサンプルを実行:

>>> s = SlicableDict(a=1, b=2, c=3, d=4) 
>>> s 
SlicableDict([('a', 1), ('c', 3), ('b', 2), ('e', 5), ('d', 4), ('f', 6)]) 
>>> s['a':'c'] 
[('a', 1)] 
>>> s['a':] 
[('a', 1), ('c', 3), ('b', 2), ('e', 5), ('d', 4)] 
>>> s[:'a'] 
[] 
>>> s['a':'f':2] 
[('a', 1), ('b', 2), ('d', 4)] 
+5

「OrderedDict」の「配管」を公開する素晴らしい答え。あなたの答えは私よりもはるかに速いと期待していますが、 "OrderedDict"の実装の変更に基づいて(名前の変更や名前の不一致による)、私が使った "磁器"の実装はおそらく安全ですバージョン間での破損はありますが、これほど早くはありません。 –

+0

ポテトではなく機能を実際に実装してくれてありがとう!私はPythonのソースでOrderedDictの実装を見ていましたが、私はgetsliceの代わりにgetitemを使うべきであるという事実を見逃していました。 –

+2

@thefoureye 'self._OrderedDict__root'ではなく' getattr'とは何ですか? – Veedrac

関連する問題