¿Puedo crear una “vista” en una lista de Python?

Tengo una lista grande l . Quiero crear una vista del elemento 4 al 6. Puedo hacerlo con un segmento de secuencia.

 >>> l=range(10) >>> lv=l[3:6] >>> lv [3, 4, 5] 

Sin embargo lv es copia de una porción de l. Si cambio la lista subyacente, lv no refleja el cambio.

 >>> l[4] = -1 >>> lv [3, 4, 5] 

A la inversa, quiero que se modifique también en lv. Aparte de eso, el tamaño de la lista no se va a cambiar.

No estoy deseando construir una gran clase para hacer esto. Solo espero que otros gurús de Python conozcan algún truco de lenguaje oculto. Idealmente espero que le guste la aritmética de punteros en C.

 int lv[] = l + 3; 

No hay una clase de “segmento de lista” en la biblioteca estándar de Python (ni una está integrada). Por lo tanto, necesita una clase, aunque no es necesario que sea grande, especialmente si está contento con un segmento “de solo lectura” y “compacto”. P.ej:

 import collections class ROListSlice(collections.Sequence): def __init__(self, alist, start, alen): self.alist = alist self.start = start self.alen = alen def __len__(self): return self.alen def adj(self, i): if i<0: i += self.alen return i + self.start def __getitem__(self, i): return self.alist[self.adj(i)] 

Esto tiene algunas limitaciones (no es compatible con "cortar una porción") pero para la mayoría de los propósitos podría estar bien.

Para hacer esta secuencia r / w necesita agregar __setitem__ , __delitem__ , e insert :

 class ListSlice(ROListSlice): def __setitem__(self, i, v): self.alist[self.adj(i)] = v def __delitem__(self, i, v): del self.alist[self.adj(i)] self.alen -= 1 def insert(self, i, v): self.alist.insert(self.adj(i), v) self.alen += 1 

Tal vez solo use una matriz numpy:

 In [19]: import numpy as np In [20]: l=np.arange(10) 

Los arreglos básicos de numpy devuelven una vista , no una copia:

 In [21]: lv=l[3:6] In [22]: lv Out[22]: array([3, 4, 5]) 

Alterar l afecta a lv :

 In [23]: l[4]=-1 In [24]: lv Out[24]: array([ 3, -1, 5]) 

Y alterando lv afecta a l :

 In [25]: lv[1]=4 In [26]: l Out[26]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) 

Puede hacerlo creando su propio generador utilizando la referencia de lista original.

 l = [1,2,3,4,5] lv = (l[i] for i in range(1,4)) lv.next() # 2 l[2]=-1 lv.next() # -1 lv.next() # 4 

Sin embargo, al ser un generador, solo puede recorrer la lista una vez, hacia adelante y explotará si elimina más elementos de los que solicitó con range .

https://gist.github.com/mathieucaroff/0cf094325fb5294fb54c6a577f05a2c1

El enlace anterior es una solución basada en la capacidad del rango Python 3 para ser cortado e indexado en tiempo constante.

Admite segmentación, comparsión de igualdad, conversión de cadenas ( __str__ ) y reproductores ( __repr__ ), pero no admite la asignación.

La creación de un SliceableSequenceView de un SliceableSequenceView no ralentizará los tiempos de acceso ya que este caso se detecta.

sequenceView.py

 # stackoverflow.com/q/3485475/can-i-create-a-view-on-a-python-list try: from collections.abc import Sequence except ImportError: from collections import Sequence # pylint: disable=no-name-in-module class SliceableSequenceView(Sequence): """ A read-only sequence which allows slicing without copying the viewed list. Supports negative indexes. Usage: li = list(range(100)) s = SliceableSequenceView(li) u = SliceableSequenceView(li, slice(1,7,2)) v = s[1:7:2] w = s[-99:-93:2] li[1] += 10 assert li[1:7:2] == list(u) == list(v) == list(w) """ __slots__ = "seq range".split() def __init__(self, seq, sliced=None): """ Accept any sequence (such as lists, strings or ranges). """ if sliced is None: sliced = slice(len(seq)) l = looksSliceable = True l = l and hasattr(seq, "seq") and isinstance(seq.seq, Sequence) l = l and hasattr(seq, "range") and isinstance(seq.range, range) looksSliceable = l if looksSliceable: self.seq = seq.seq self.range = seq.range[sliced] else: self.seq = seq self.range = range(len(seq))[sliced] def __len__(self): return len(self.range) def __getitem__(self, i): if isinstance(i, slice): return SliceableSequenceView(self.seq, i) return self.seq[self.range[i]] def __str__(self): r = self.range s = slice(r.start, r.stop, r.step) return str(self.seq[s]) def __repr__(self): r = self.range s = slice(r.start, r.stop, r.step) return "SliceableSequenceView({!r})".format(self.seq[s]) def equal(self, otherSequence): if self is otherSequence: return True if len(self) != len(otherSequence): return False for v, w in zip(self, otherSequence): if v != w: print(v, w) return False return True 

Edición: The object argument must be an object that supports the buffer call interface (such as strings, arrays, and buffers). – asi que no, tristemente

Creo que el tipo de búfer es lo que estás buscando.

Pegando ejemplo de página enlazada:

 >>> s = bytearray(1000000) # a million zeroed bytes >>> t = buffer(s, 1) # slice cuts off the first byte >>> s[1] = 5 # set the second element in s >>> t[0] # which is now also the first element in t! '\x05' 

Tan pronto como tome una porción de una lista, estará creando una nueva lista. De acuerdo, contendrá los mismos objetos, siempre que los objetos de la lista sean los mismos, pero si modifica un sector, la lista original no se modifica.

Si realmente desea crear una vista modificable, puede imaginar una nueva clase basada en collection.MutableSequence

Este podría ser un punto de partida para una sub-lista con todas las funciones: procesa correctamente los índices de sector, pero al menos carece de especificación para el procesamiento de índices negativos:

 class Sublist(collections.MutableSequence): def __init__(self, ls, beg, end): self.ls = ls self.beg = beg self.end = end def __getitem__(self, i): self._valid(i) return self.ls[self._newindex(i)] def __delitem__(self, i): self._valid(i) del self.ls[self._newindex(i)] def insert(self, i, x): self._valid(i) self.ls.insert(i+ self.beg, x) def __len__(self): return self.end - self.beg def __setitem__(self, i, x): self.ls[self._newindex(i)] = x def _valid(self, i): if isinstance(i, slice): self._valid(i.start) self._valid(i.stop) elif isinstance(i, int): if i<0 or i>=self.__len__(): raise IndexError() else: raise TypeError() def _newindex(self, i): if isinstance(i, slice): return slice(self.beg + i.start, self.beg + i.stop, i.step) else: return i + self.beg 

Ejemplo:

 >>> a = list(range(10)) >>> s = Sublist(a, 3, 8) >>> s[2:4] [5, 6] >>> s[2] = 15 >>> a [0, 1, 2, 3, 4, 15, 6, 7, 8, 9] 

Si va a acceder a la “vista” de forma secuencial, puede utilizar itertools.islice (..) Puede ver la documentación para obtener más información .

 l = [1, 2, 3, 4, 5] d = [1:3] #[2, 3] d = itertools.islice(2, 3) # iterator yielding -> 2, 3 

No puede acceder a elementos individuales para cambiarlos en la división y si cambia la lista, debe volver a llamar a isclice (..).

Subclase the more_itertools.SequenceView para afectar las vistas mediante la mutación de secuencias y viceversa.

Código

 import more_itertools as mit class SequenceView(mit.SequenceView): """Overload assignments in views.""" def __setitem__(self, index, item): self._target[index] = item 

Manifestación

 >>> seq = list(range(10)) >>> view = SequenceView(seq) >>> view SequenceView([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> # Mutate Sequence -> Affect View >>> seq[6] = -1 >>> view[5:8] [5, -1, 7] >>> # Mutate View -> Affect Sequence >>> view[5] = -2 >>> seq[5:8] [-2, -1, 7] 

more_itertools es una biblioteca de terceros. Instalar via > pip install more_itertools .

Podrías editar: no hacer algo así.

 shiftedlist = type('ShiftedList', (list,), {"__getitem__": lambda self, i: list.__getitem__(self, i + 3)} )([1, 2, 3, 4, 5, 6]) 

Al ser esencialmente de una sola línea, no es muy Pythonic, pero esa es la esencia básica.

edición: Me di cuenta tardíamente de que esto no funciona porque list() esencialmente hará una copia superficial de la lista que se aprobó. Así que esto terminará siendo más o menos lo mismo que cortar la lista. En realidad menos, debido a una anulación faltante de __len__ . Tendrá que usar una clase de proxy; Vea la respuesta del Sr. Martelli para los detalles.