модуле itertools.реализация islice-эффективное нарезание списка

ранее я пытался ответить на вопрос, где я хотел повторить срез списка как можно эффективнее.

for x in lst[idx1:]:

не идеально, поскольку он создает копию (в общем, это O(n)). Моей следующей мыслью было использовать itertools.islice. Но если вы посмотрите на документацию, то окажется, что islice будем называть next пока он не найдет индекс, который он ищет, в какой момент он начнет давать значения. Это тоже O(n). Кажется, что есть оптимизация, которая доступна здесь, если объект передан в islice это list или tuple -- кажется, что вы можете перебирать "срез" напрямую (в C), фактически не делая копию. Мне было любопытно, если эта оптимизация в источник, но я ничего не нашел. Я не очень хорошо знаком с C и исходным деревом python, поэтому вполне возможно, что я пропустил его.

мой вопрос такой:

есть ли способ перебирать список "срез", не делая копию среза списка и не сжигая кучу нежелательных элементов (в оптимизированной реализации C)?

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

def myslice(obj,start,stop,stride):
    for i in xrange(start,stop,stride):
        yield obj[i]

но это определенно не будет бить оптимизированную реализацию C.


Если вам интересно почему мне нужно это просто зацикливание на срезе напрямую, рассмотрим разницу между:

takewhile(lambda x: x == 5, lst[idx:])  #copy's the tail of the list unnecessarily

и

takewhile(lambda x: x == 5, islice(lst,idx,None)) #inspects the head of the list unnecessarily 

и наконец:

takewhile(lambda x: x == 5, magic_slice(lst,idx,None)) #How to create magic_slice???

4 ответов


есть ли способ перебирать список "срез", не делая копию среза списка и не сжигая кучу нежелательных элементов (в оптимизированной реализации C)?

Да есть, если вы пишете, что реализация C. на Cython делает это очень легко.

cdef class ListSlice(object):
    cdef object seq
    cdef Py_ssize_t start, end

    def __init__(self, seq, Py_ssize_t start, Py_ssize_t end):
        self.seq = seq
        self.start = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.start == self.end:
            raise StopIteration()
        r = self.seq[self.start]
        self.start += 1
        return r

Я думаю, стоит упомянуть, что numpy-срезы не копируются (они создают представление на базовый массив). Поэтому, если вы можете использовать массивы NumPy для своих данных, это решит проблему. Кроме того, вы можете получить дополнительные улучшения производительности за счет векторизации.


Если вы используете PyPy (который вы можете, так как вы заботитесь о производительности), они оптимизируют нарезку строк, чтобы не копировать:http://doc.pypy.org/en/latest/interpreter-optimizations.html


islice является функцией от itertools модуль, поэтому он работает (и определенно должен работать) с iterators в общем, не только с lists. Таким образом, вы не можете найти свою оптимизацию в itertools исходный код, потому что он должен работать с любой итератор.

правильный подход в вашем случае это:

def magic_slice(lst, start, end=None):
    for pos in xrange(start, (end or len(lst)):
        yield lst[pos]

takewhile будет называть ваш генератор "один за другим", и он будет yield новые значения-та же "скорость", что и для общего списка ходьбы + xrange итерации. Так накладные расходы при такой реализации минимальны. Если вам нужно больше - вы можете переписать эту функцию на C, но я не вижу много преимуществ для этого.