代码之家  ›  专栏  ›  技术社区  ›  Wai Yip Tung

我可以在Python列表上创建一个“视图”吗?

  •  38
  • Wai Yip Tung  · 技术社区  · 14 年前

    我有一张大单子 l . 我想创建一个从元素4到元素6的视图。我可以用序列片。

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

    然而 lv 是一份

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

    反之亦然,我想修改一下 反映 也。除此之外,列表大小不会改变。

    我不期待建立一个大类来做这件事。我只是希望其他Python大师可能知道一些隐藏的语言技巧。理想情况下,我希望它可以像C中的指针算法:

    int lv[] = l + 3;
    
    9 回复  |  直到 3 年前
        1
  •  36
  •   Uyghur Lives Matter    9 年前

    Python标准库中没有“list slice”类(也没有内置的)。因此,您确实需要一个类,尽管它不需要很大——特别是如果您满足于“只读”和“紧凑”切片的话。例如。:

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

    这有一些限制(不支持“切片”),但在大多数情况下可能是可以的。

    要使这个序列r/w,您需要添加 __setitem__ , __delitem__ 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
  •  30
  •   unutbu    14 年前

    也许只需要使用numpy数组:

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

    returns a view ,不是副本:

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

    改变 l lv

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

    影响

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

    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
    

    然而,作为一个生成器,您只能浏览列表一次,如果您删除的元素比您所请求的多,它将爆炸 range .

        4
  •  4
  •   pylang    6 年前

    子类化 more_itertools.SequenceView

    代码

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

    演示

    >>> 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 是第三方图书馆。通过安装 > pip install more_itertools

        5
  •  4
  •   Mathieu CAROFF    4 年前

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

    上面的链接是一个基于python3范围的解决方案,可以被切片和 以恒定时间索引。

    它支持切片、相等比较、字符串转换( __str__ ),和 复制者( __repr__

    创建SliceableSequenceView的SliceableSequenceView不会减慢速度

    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))
            ls = looksSliceable = True
            ls = ls and hasattr(seq, "seq") and isinstance(seq.seq, Sequence)
            ls = ls and hasattr(seq, "range") and isinstance(seq.range, range)
            looksSliceable = ls
            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:
                    return False
            return True
    
        6
  •  1
  •   Community Mr_and_Mrs_D    7 年前

    The object argument must be an object that supports the buffer call interface (such as strings, arrays, and buffers). -所以不,很遗憾。

    buffer type 就是你要找的。

    从链接页粘贴示例:

    >>> 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' 
    
        7
  •  1
  •   Serge Ballesta    9 年前

    一旦你从一个列表中选取一个片段,你就会创建一个新的列表。好的,它将包含相同的对象,因此只要涉及列表中的对象,它就将是相同的,但是如果修改切片,则原始列表将保持不变。

    collection.MutableSequence

    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
    

    例子:

    >>> 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]
    
        8
  •  0
  •   Community Mr_and_Mrs_D    7 年前

    像这样做

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

    编辑: 我迟迟才意识到这是行不通的,因为 list() __len__ . 您需要使用代理类;看见 Mr. Martelli's answer

        9
  •  0
  •   user2846495    5 年前

    实际上,自己使用 range *您可以分割一个范围,它会为您完成所有复杂的运算:

    >>> range(20)[10:]
    range(10, 20)
    >>> range(10, 20)[::2]
    range(10, 20, 2)
    >>> range(10, 20, 2)[::-3]
    range(18, 8, -6)
    

    因此,您只需要一个对象类,其中包含对原始序列的引用和一个范围。下面是这样一个类的代码(我希望不是太大):

    class SequenceView:
    
        def __init__(self, sequence, range_object=None):
            if range_object is None:
                range_object = range(len(sequence))
            self.range    = range_object
            self.sequence = sequence
    
        def __getitem__(self, key):
            if type(key) == slice:
                return SequenceView(self.sequence, self.range[key])
            else:
                return self.sequence[self.range[key]]
    
        def __setitem__(self, key, value):
            self.sequence[self.range[key]] = value
    
        def __len__(self):
            return len(self.range)
    
        def __iter__(self):
            for i in self.range:
                yield self.sequence[i]
    
        def __repr__(self):
            return f"SequenceView({self.sequence!r}, {self.range!r})"
    
        def __str__(self):
            if type(self.sequence) == str:
                return ''.join(self)
            elif type(self.sequence) in (list, tuple):
                return str(type(self.sequence)(self))
            else:
                return repr(self)
    

    用法:

    >>> p = list(range(10))
    >>> q = SequenceView(p)[3:6]
    >>> print(q)
    [3, 4, 5]
    >>> q[1] = -1
    >>> print(q)
    [3, -1, 5]
    >>> print(p)
    [0, 1, 2, 3, -1, 5, 6, 7, 8, 9]
    

        10
  •  -2
  •   Adam Gillessen    8 年前

    如果要按顺序访问“视图”,则可以使用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