如何实现 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 具有这些属性)。