2010-08-14 17 views
34

Tengo una lista grande l. Quiero crear una vista del elemento 4 al 6. Puedo hacerlo con una secuencia de corte.¿Puedo crear una "vista" en una lista de Python?

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

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

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

Viceversa Quiero que la modificación en lv también se refleje en l. Aparte de eso, el tamaño de la lista no va a cambiar.

No tengo muchas ganas de construir una gran clase para hacer esto. Solo espero que otros gurús de Python conozcan algún truco de lenguaje oculto. Lo ideal sería que espero que pueda recibir la aritmética de punteros en C.

int lv[] = l + 3; 
+0

Para cualquiera que se encuentre con esta pregunta como yo, [memoryview] (https://docs.python.org/2/library/stdtypes.html#memoryview) ahora ofrece esta capacidad. – robert

+0

@robert ¿Cómo? La 'memoryview' solo funciona para objetos con interfaz de búfer y la lista no es uno de ellos. – zegkljan

+0

En el ejemplo proporcionado aquí, debe usar un 'bytearray' en lugar de una lista. También puede envolver la lista en 'bytearray'. – robert

Respuesta

28

No hay una clase de "sector de lista" en la biblioteca estándar de Python (ni está incorporada). Entonces, necesitas una clase, aunque no necesita ser grande, especialmente si estás satisfecho con un segmento "solo" y "compacto". 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 soporta "cortar una rebanada"), pero para la mayoría de los propósitos podría estar bien.

Para hacer esta secuencia r/w es necesario agregar __setitem__, __delitem__ y 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 
+2

'__length__' debe ser' __len__', ¿no? – intuited

+0

Podría hacer algo como 'def __slice __ (self, * args, ** kwargs): return (self.alist [self.start: self.start + self.alen]) .__ slice __ (* args, ** kwargs)' para apoyar cosas como rebanar? Básicamente, pasar la solicitud a un sector creado a pedido. – Amber

+3

Pero si haces 'alist.insert (0, something)' ¡la rebanada se mueve! Eso podría o no ser un problema ... –

5

Puede hacerlo mediante la creación de su propio generador utilizando la referencia 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, esto es un generador, sólo se puede ir a través de la lista una vez, hacia delante y que va a explotar si se quita más elementos de lo que ha sido recibida con range.

0

Usted podría edición: no hacer algo como

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

Siendo esencialmente un one-liner, no es muy Pythonic, pero esa es la esencia básica.

edición: me he dado cuenta tardíamente que esto no funciona porque list() esencialmente va a hacer una copia superficial de la lista que ha pasado. 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__. Necesitarás usar una clase de proxy; ver Mr. Martelli's answer para los detalles.

27

Tal vez sólo tiene que utilizar una matriz numpy:

In [19]: import numpy as np 

In [20]: l=np.arange(10) 

rebanar básico matrices numpy returns a view, no una copia:

In [21]: lv=l[3:6] 

In [22]: lv 
Out[22]: array([3, 4, 5]) 

La alteración de l afecta lv:

In [23]: l[4]=-1 

In [24]: lv 
Out[24]: array([ 3, -1, 5]) 

y alterando lv afecta l:

In [25]: lv[1]=4 

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

Editar:The object argument must be an object that supports the buffer call interface (such as strings, arrays, and buffers). - así que no, por desgracia.

Creo que buffer type es lo que estás buscando.

pegar ejemplo de la 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' 
+0

no hay buffer ''() 'incorporado en Python 3.' memoryview() 'podría ser usado en su lugar. – jfs

+0

Además, esto inspecciona los bytes en memoria del área: las listas de Python contienen objetos (que "en la memoria" apuntan a los objetos) entonces, definitivamente, este sería un enfoque incorrecto. Uno debería usar 'ctypes' , y vuelva a ejecutar todo el trabajo indirecto de Pointer, como si estuviera codificando en C, que Python lo hace gratis. – jsbueno

1

Tan pronto como usted tomar una rebanada de una lista, que será la creación de una nueva lista. De acuerdo, contendrá los mismos objetos, por lo que siempre y cuando se trate de objetos de la lista, sería lo mismo, pero si modifica un sector, la lista original no se modifica.

Si realmente desea crear una vista modificable, se podría imaginar una nueva clase basada en collection.MutableSequence

Esto podría ser un punto de partida para una lista de sub ofrecido completo - se procesa correctamente los índices de división, pero al menos es carente 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] 
+0

Esta es una respuesta directa a otra pregunta que se cerró como duplicado de esta. Como otras respuestas de aquí eran relevantes, preferí agregarlas aquí –

0

Si va a ser el acceso XX e "ver" secuencialmente, entonces puede simplemente usar itertools.islice (..) You can see the documentation for more info.

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

No se puede acceder a los elementos individuales para cambiarlos en el corte y si lo hace cambiar la lista que tenga que volver a la llamada isclice (..).

0

Subclase more_itertools.SequenceView para afectar las vistas mediante secuencias de mutación 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 

demostració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 a través de > pip install more_itertools.

Cuestiones relacionadas