Подтвердить что ты не робот

Могу ли я создать "представление" в списке Python?

У меня есть большой список l. Я хочу создать представление из элементов с 4 по 6. Я могу сделать это с помощью фрагмента последовательности.

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

Однако lv является копией фрагмента l. Если я изменю базовый список, lv не отражает изменения.

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

И наоборот, я хочу, чтобы модификация на lv отражалась и в l. Кроме того, размер списка не будет изменен.

Я не собираюсь создавать большой класс для этого. Я просто надеюсь, что другие гуру-питоны могут знать какой-то скрытый языковой трюк. В идеале я надеюсь, что он может любить арифметику указателя в C.

int lv[] = l + 3;
4b9b3361

Ответ 1

В стандартной библиотеке Python нет ни одного класса "среза списка" (ни один встроенный). Итак, вам нужен класс, хотя он не должен быть большим - особенно если вы довольствуетесь "readonly" и "компактным" фрагментом. Например:.

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

Возможно, просто используйте массив numpy:

In [19]: import numpy as np

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

Базовые массивы numeries sca возвращает представление, а не копию:

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

И изменение lv влияет на l:

In [25]: lv[1]=4

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

Ответ 3

Это можно сделать, создав собственный генератор, используя исходную ссылку.

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

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

Выше ссылка представляет собой решение, основанное на способности диапазона Python 3 быть разрезаны и проиндексированы в постоянное время.

Он поддерживает нарезку, сравнение на равенство, приведение строк (__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))
        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

Ответ 5

Изменить: The object argument must be an object that supports the buffer call interface (such as strings, arrays, and buffers). - так нет, к сожалению.

Я думаю, что тип буфера - это то, что вы ищете.

Вставка примера со связанной страницы:

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

Ответ 6

Как только вы возьмете фрагмент из списка, вы создадите новый список. Хорошо, он будет содержать те же объекты, что и объекты списка, будет одинаковым, но если вы измените фрагмент, исходный список не изменится.

Если вы действительно хотите создать изменяемое представление, вы можете представить себе новый класс на основе 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]

Ответ 7

more_itertools.SequenceView подкласс 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.

Ответ 8

Вы можете изменить: не делать что-то вроде

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

Будучи по существу однострочным, это не очень Pythonic, но это основной смысл.

edit: Я запоздало понял, что это не работает, потому что list() будет по существу делать мелкую копию списка, который он передал. Таким образом, это будет более или менее таким же, как просто нарезка списка. На самом деле меньше, из-за отсутствия переопределения __len__. Вам нужно будет использовать прокси-класс; см. Г-н. Martelli ответьте для деталей.

Ответ 9

Если вы собираетесь получать доступ к "представлению" последовательно, вы можете просто использовать itertools.islice(..) Вы можете увидеть документацию для получения дополнительной информации.

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

Вы не можете получить доступ к отдельным элементам, чтобы изменить их в срезе, и если вы измените список, вы должны повторно вызвать isclice (..).

Ответ 10

На самом деле это не так уж сложно реализовать самостоятельно, используя 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)

(Это было собрано примерно через 5 минут, поэтому убедитесь, что вы тщательно проверили его, прежде чем использовать его где-нибудь важно.)

Использование:

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

* в Python 3