python python-2.7 dictionary python-internals ordereddictionary

deque python



¿Cómo se puede dividir con claves de cadena en lugar de enteros en un OrderedDict de Python? (3)

Esta es la implementación real de la función de división que está esperando.

OrderedDict mantiene internamente el orden de las claves en forma de una lista doblemente enlazada. Citando el comentario real de 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].

Ahora, para dividir el diccionario, necesitamos iterar la lista doblemente enlazada, __root , que en realidad es una variable privada, protegida por el mecanismo de manipulación de nombres .

Nota: Esto implica que el nombre de hacky se desenreda para usar las estructuras de datos internas de 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)

Pocas muestras de muestra:

>>> 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)]

Dado que un OrderedDict tiene las características de una lista (con elementos ordenados) y un diccionario (con claves en lugar de índices), parece natural que se pueda cortar con claves.

>>> 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

Lo interesante de esto es que no es el error que OrderedDictionary.__getslice__ debido a OrderedDictionary.__getslice__ no se está implementando. Intenté agregar mi propio método __getslice__ a OrderedDict , pero sigo encontrando este problema TypeError. Parece que Python está haciendo algún tipo de comprobación de tipos para imponer que las claves de __getslice__ sean solo números enteros, incluso antes de __getslice__ a la función __getslice__ , ¡qué impía!

>>> 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!

Entonces, mi pregunta es: ¿por qué no puedo implementar segmentos no int, qué tipo de comprobación de tipos impide que las teclas de segmento lleguen a mi función __getslice__ y puedo anularlo implementando mi BetterOrderedDict en C con enlaces?


Prueba esta implementación (muy fea)

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)])

NB: esto probablemente solo funciona porque en este ejemplo, OrderedDict no solo se ordenó, sino que también se ordenó. En un diccionario sin clasificar, la división ''a'':''c'' no necesariamente contiene ''b'' , por lo que mi if key.start <= k <= key.stop probablemente falla. El siguiente código debe respetar eso:

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


__getslice__ es una forma obsoleta de implementar la segmentación. En su lugar, debe manejar objetos de __getitem__ con __getitem__ :

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)''

Y si necesita más que papa, puede implementar las tres operaciones de corte de la siguiente manera:

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)