简体   繁体   English

如何实现 class 实例的延迟切片?

[英]How to implement the lazy slicing of a class instance?

I am building a class which has underpinning it a list of int s, its __getitem__ method is then a reasonably computationally involved function outputting a list of potentially large size.我正在构建一个 class ,它有一个int列表的基础,它的__getitem__方法然后是一个合理的计算参与 function 输出可能大尺寸的列表。 Here is the code:这是代码:

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

My code is currently not working as it always returns a generator, and this messes up iterating over an instance of the class.我的代码目前无法正常工作,因为它总是返回一个生成器,这会弄乱对 class 实例的迭代。 However upon slicing, I do not wish it to do all the computations and store it, as this would make it consume a lot more memory.但是在切片时,我不希望它完成所有计算并存储它,因为这会使其消耗更多的 memory。 An example of what I wish to do:我想做的一个例子:

from math import prod

test = A(10**7)
out = 0
for i in test[10**6:10**7]:
    out += prod(i)

How can I make slices work efficiently?如何使切片有效地工作?

Instead of returning a generator, return a view .与其返回生成器,不如返回一个视图 This is an object that conceptually represents the sequence of elements in question.这是一个 object,它在概念上表示相关元素的序列。 We can do this by storing a reference to the original A instance, plus a range that encodes the instances.我们可以通过存储对原始 A 实例的引用以及对实例进行编码的范围来做到这一点。 When we index into the view, we can ask the range which original index (or indices) are involved.当我们对视图进行索引时,我们可以询问涉及哪些原始索引(或多个索引)的范围。

Supposing we set up the general structure:假设我们建立了一般结构:

class A(list):
    def __init__(self, n):
        super().__init__(self.li)
        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`

Then our view could look like:那么我们的视图可能如下所示:

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)

The idea is that if we receive an integer index, we translate it through the range object into an index for the original A , and call back to its __getitem__ (with an integer this time).这个想法是,如果我们收到 integer 索引,我们将其通过range object 转换为原始A的索引,并回调其__getitem__ (此时使用 Z157DB7DF530023575515D366C9B6)。 If we receive another slice , we use it to slice our range into a sub-range, and make another view.如果我们收到另一个slice ,我们使用它将我们的范围分割成一个子范围,并创建另一个视图。

Note that your A class should already be iterable, due to inheriting from list .请注意,由于从list继承,您的 A class 应该已经是可迭代的。 To make the view iterable (and also get the in operator, forward and reverse iteration, .index and .count methods for free), you can inherit from collections.abc.Sequence .要使视图可迭代(并免费获得in运算符、正向和反向迭代、 .index.count方法),您可以从collections.abc.Sequence继承。 You just need a __getitem__ and __len__ - both easy to implement, as above - and the base class will do the rest (while also advertising to isinstance that your class has these properties).您只需要一个__getitem____len__ - 两者都易于实现,如上所述 - 基础 class 将执行 rest (同时还向实例宣传您的isinstance具有这些属性)

I'm not totally sure what you're asking.我不完全确定你在问什么。 Does this work for your use case?这对您的用例有用吗? Items will be generated lazily and not stored (unless the caller stores them).项目将延迟生成而不存储(除非调用者存储它们)。

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:

    https://stackoverflow.com/questions/13855288/turn-slice-into-range
    '''
    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)

Output: Output:

99990
20
30
40
50
60
70
80
90

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM