如何实现 class 实例的惰性切片?
How to implement the lazy slicing of a class instance?
我正在构建一个 class,它有一个 int
的列表,它的 __getitem__
方法是一个合理的计算涉及的函数,输出一个可能很大的列表。这是代码:
Class A(list):
def __init__(self, n):
self.li = [i for i in range(0, n)]
super().__init__(self.li)
self._ind = 0
def __getitem__(self, item):
assert (type(item) is int and item >= 0) or type(item) is slice
if type(item) is slice:
start, stop, step = item.start, item.stop, item.step
if stop is None:
stop = len(self)
if step is not None:
for i in range(start, stop, step):
yield self[i]
else:
for i in range(start, stop):
yield self[i]
if item == 0 or item == 1:
return []
out = []
while item != 1:
if super().__getitem__(item):
p = super().__getitem__(item)
else:
out.append(item)
return out
if p not in out:
out.append(p)
item //= p
return out
我的代码目前无法正常工作,因为它总是 returns 一个生成器,这会搞乱对 class 实例的迭代。但是,在切片时,我不希望它进行所有计算并存储它,因为这会消耗更多内存。我想做的一个例子:
from math import prod
test = A(10**7)
out = 0
for i in test[10**6:10**7]:
out += prod(i)
如何让切片高效工作?
我不太确定你在问什么。这对您的用例有用吗?项目将延迟生成而不存储(除非调用者存储它们)。
class LazyCollection:
def __getitem__(self, key):
if isinstance(key, int):
return self.get_single_item(key)
elif isinstance(key, slice):
def my_generator():
for index in slice_to_range(key):
yield self.get_single_item(index)
return my_generator()
def get_single_item(self, index):
# (Example! Your logic here.)
return index * 10
def slice_to_range(my_slice):
'''
More options for implementing this:
'''
start = my_slice.start if my_slice.start is not None else 0
stop = my_slice.stop
step = my_slice.step if my_slice.step is not None else 1
return range(start, stop, step)
coll = LazyCollection()
# Get a single item
print(coll[9999])
# Get a slice
for x in coll[2:10]:
print(x)
输出:
99990
20
30
40
50
60
70
80
90
不是 return 生成器,而是 return 视图。这是一个在概念上表示相关元素序列的对象。我们可以通过存储对原始 A 实例的引用以及对实例进行编码的范围来做到这一点。当我们索引到视图中时,我们可以询问原始索引(或索引)涉及的范围。
假设我们设置了通用结构:
class A(list):
def __init__(self, n):
super().__init__()
self[:] = [i for i in range(0, n)]
def _at(self, idx):
# custom logic here to return the correct value for self[idx]
def __getitem__(self, idx):
if isinstance(idx, int):
return self._at(idx)
elif isinstance(idx, slice):
# The `indices` method of a slice object converts to
# start, stop, step values which we can use to construct a range.
return A_view(self, range(*idx.indices(len(self))))
else:
raise TypeError # this is not an appropriate use for `assert`
那么我们的视图可能如下所示:
class A_view:
def __init__(self, original, indices):
self._original, self._indices = original, indices
def __getitem__(self, idx):
if isinstance(idx, int):
return self._original[self._indices[idx]]
elif isinstance(idx, slice):
return A_view(self._original, self._indices[idx])
else:
raise TypeError
def __len__(self):
return len(self._indices)
这个想法是,如果我们收到一个整数索引,我们通过range
对象将它翻译成原始A
的索引,并回调到它的__getitem__
(这次是整数)。如果我们收到另一个 slice
,我们用它来将我们的范围分割成 sub-range,并制作另一个视图。
请注意,由于继承自 list
,您的 A class 应该已经是可迭代的了。要使视图可迭代(并免费获得 in
运算符、正向和反向迭代、.index
和 .count
方法),您可以继承自 collections.abc.Sequence
。您只需要一个 __getitem__
和 __len__
- 两者都易于实现,如上所述 - 基础 class 将完成剩下的工作(同时还向 isinstance
宣传您的 class 具有这些属性)。
我正在构建一个 class,它有一个 int
的列表,它的 __getitem__
方法是一个合理的计算涉及的函数,输出一个可能很大的列表。这是代码:
Class A(list):
def __init__(self, n):
self.li = [i for i in range(0, n)]
super().__init__(self.li)
self._ind = 0
def __getitem__(self, item):
assert (type(item) is int and item >= 0) or type(item) is slice
if type(item) is slice:
start, stop, step = item.start, item.stop, item.step
if stop is None:
stop = len(self)
if step is not None:
for i in range(start, stop, step):
yield self[i]
else:
for i in range(start, stop):
yield self[i]
if item == 0 or item == 1:
return []
out = []
while item != 1:
if super().__getitem__(item):
p = super().__getitem__(item)
else:
out.append(item)
return out
if p not in out:
out.append(p)
item //= p
return out
我的代码目前无法正常工作,因为它总是 returns 一个生成器,这会搞乱对 class 实例的迭代。但是,在切片时,我不希望它进行所有计算并存储它,因为这会消耗更多内存。我想做的一个例子:
from math import prod
test = A(10**7)
out = 0
for i in test[10**6:10**7]:
out += prod(i)
如何让切片高效工作?
我不太确定你在问什么。这对您的用例有用吗?项目将延迟生成而不存储(除非调用者存储它们)。
class LazyCollection:
def __getitem__(self, key):
if isinstance(key, int):
return self.get_single_item(key)
elif isinstance(key, slice):
def my_generator():
for index in slice_to_range(key):
yield self.get_single_item(index)
return my_generator()
def get_single_item(self, index):
# (Example! Your logic here.)
return index * 10
def slice_to_range(my_slice):
'''
More options for implementing this:
'''
start = my_slice.start if my_slice.start is not None else 0
stop = my_slice.stop
step = my_slice.step if my_slice.step is not None else 1
return range(start, stop, step)
coll = LazyCollection()
# Get a single item
print(coll[9999])
# Get a slice
for x in coll[2:10]:
print(x)
输出:
99990
20
30
40
50
60
70
80
90
不是 return 生成器,而是 return 视图。这是一个在概念上表示相关元素序列的对象。我们可以通过存储对原始 A 实例的引用以及对实例进行编码的范围来做到这一点。当我们索引到视图中时,我们可以询问原始索引(或索引)涉及的范围。
假设我们设置了通用结构:
class A(list):
def __init__(self, n):
super().__init__()
self[:] = [i for i in range(0, n)]
def _at(self, idx):
# custom logic here to return the correct value for self[idx]
def __getitem__(self, idx):
if isinstance(idx, int):
return self._at(idx)
elif isinstance(idx, slice):
# The `indices` method of a slice object converts to
# start, stop, step values which we can use to construct a range.
return A_view(self, range(*idx.indices(len(self))))
else:
raise TypeError # this is not an appropriate use for `assert`
那么我们的视图可能如下所示:
class A_view:
def __init__(self, original, indices):
self._original, self._indices = original, indices
def __getitem__(self, idx):
if isinstance(idx, int):
return self._original[self._indices[idx]]
elif isinstance(idx, slice):
return A_view(self._original, self._indices[idx])
else:
raise TypeError
def __len__(self):
return len(self._indices)
这个想法是,如果我们收到一个整数索引,我们通过range
对象将它翻译成原始A
的索引,并回调到它的__getitem__
(这次是整数)。如果我们收到另一个 slice
,我们用它来将我们的范围分割成 sub-range,并制作另一个视图。
请注意,由于继承自 list
,您的 A class 应该已经是可迭代的了。要使视图可迭代(并免费获得 in
运算符、正向和反向迭代、.index
和 .count
方法),您可以继承自 collections.abc.Sequence
。您只需要一个 __getitem__
和 __len__
- 两者都易于实现,如上所述 - 基础 class 将完成剩下的工作(同时还向 isinstance
宣传您的 class 具有这些属性)。