views:

129

answers:

5

I have a large list l. I want to create a view from element 4 to 6. I can do it with sequence slice.

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

However lv is copy of a slice of l. If I change the underlying list, lv does not reflect the change.

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

Vice versa I want modification on lv reflect in l as well. Other than that the list size are not going to be changed.

I'm not look forward to build a big class to do this. I'm just hoping other Python gurus may know some hidden language trick. Ideally I hope it can like pointer arithmetic in C.

int lv[] = l + 3;
+1  A: 

You can do that by creating your own generator using the original list reference.

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

However this being a generator, you can only go through the list once, forwards and it will explode if you remove more elements than you requested with range.

viraptor
+12  A: 

There is no "list slice" class in the Python standard library (nor is one built-in). So, you do need a class, though it need not be big -- especially if you're content with a "readonly" and "compact" slice. E.g.:

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

This has some limitations (doesn't support "slicing a slice") but for most purposes might be OK.

To make this sequence r/w you need to add __setitem__, __delitem__, and 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
Alex Martelli
`__length__` should be `__len__`, no?
intuited
Could you do something like `def __slice__(self, *args, **kwargs): return (self.alist[self.start:self.start+self.alen]).__slice__(*args, **kwargs)` to support things like slicing? Basically passing through the request to a slice created on-demand.
Amber
But if you do `alist.insert(0, something)` the slice moves! That might or might not be a problem ...
THC4k
@intuited, yep, tx -- let me fix.
Alex Martelli
@Amber, `__slice__` is not a special method in Python. Slicing results in calls to `__getindex__`, `__setindex__`, `__delindex__`, so you'd have to typecheck and adjust that (easier for the getting, as your approach will delegate things OK -- harder for setting and deleting, though).
Alex Martelli
Alex Martelli
@Alex: Hm. I could have sworn that there were ways to override slicing (say, to allow for things like 2-dimensional slicing). But I could be wrong. :)
Amber
@Amber, of course you can "override slicing" -- you do that by overriding `__getitem__` (and maybe the set and del ones as well, for a type with mutable instances), and type-checking / type-switching on the "index" argument (e.g., to allow `a[1:2,3:4]`, you deal with receiving, as the "index" argument, a tuple with two items, both of them slice objects).
Alex Martelli
@Alex - that's basically what I was getting at, then - the `__slice__` bit was just something that popped to mind; but it seems like you could do the same with slicing (just override `__getitem__` to operate on the generated slice instead of the original list).
Amber
@Amber, while that can work for `__getitem__` (slowly, as it makes a redundant slice for every indexing, and `collections.Sequence` uses indexing a _lot_ -- look at its sources), it can't for `__setitem__`, and it would be truly weird if you could get but not set a slice. So, for a mutable sequence, you do have to perform type-checking and adjustments (probably in the `def adj`, so at least you need code them only once, but it's still somewhat delicate code and the OP said he didn't want "a big class";-) -- for an immutable one, only if you care at all about performance.
Alex Martelli
Thanks. It is neat to break down into a RO class and a RW class. No I don't need to insert or delete from the list. If this becomes a requirement it will turn into a whole new topic.
Wai Yip Tung
@Wai, you're welcome! Yes, using the ABCs in `collections`, for implementing each of sequences, sets, and mappings, often turns into a pair of X / MutableX (where the latter can usefully inherit from the former), which can be handy.
Alex Martelli
A: 
intuited
+5  A: 

Perhaps just use a numpy array:

In [19]: import numpy as np

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

Basic slicing numpy arrays returns a view, not a copy:

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

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

Altering l affects lv:

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

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

And altering lv affects l:

In [25]: lv[1]=4

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

Edit: The object argument must be an object that supports the buffer call interface (such as strings, arrays, and buffers). - so no, sadly.

I think buffer type is what you are looking for.

Pasting example from linked page:

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