Могу ли я создать "представление" в списке 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;
Ответы
Ответ 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