Itertools.islice выполняет итерацию по вводу, даже если остановка меньше, чем начало

Для пользовательского контейнера, обертывающего итератор, я намеревался делегировать часть своей логики itertools.islice. Одним из соображений было избежать ненужных итераций по обернутому итератору.

При вызове itertools.islice(iterable, start, stop, step) с помощью stop<=start результатом, как и ожидалось, является пустой генератор.

Но даже несмотря на то, что это не является абсолютно необходимым, itertools.islice(iterable, start, stop, step) всегда будет повторять как минимум start число раз по итерируемому объекту.

Тестовый пример для воспроизведения:

from unittest.mock import Mock
import itertools

iterableMock = Mock()
iterableMock.__iter__ = Mock(return_value=iterableMock)
iterableMock.__next__ = Mock(side_effect=range(10))

iterable = iterableMock
start = 5
stop = 0
step = None

isliceResult = list(itertools.islice(iterable, start, stop, step))
assert isliceResult == []
assert iterableMock.__next__.call_count == 0  # <= FAILS since call_count == 5

Это поведение:

  • Ожидается/задумано. Мы хотим пропускать элементы до тех пор, пока не будет достигнут старт, несмотря ни на что.
  • Просто побочный эффект текущей реализации, который влияет на производительность только в некоторых крайних случаях.
  • Потенциальное улучшение, которое можно решить без опасений.

Документация может немного вводить в заблуждение/неправильно истолковывать ожидаемое поведение.

  • элементы из итерации пропускаются до тех пор, пока не будет достигнуто начало
  • он останавливается в указанной позиции

Предлагаемая «эквивалентная реализация» и исходный код явно повторяются до start-1. Параметр stop учитывается только впоследствии.

«Просто побочный эффект текущей реализации, который влияет на производительность только в некоторых крайних случаях». <- это

jsbueno 24.06.2024 23:51

Мне кажется, это дизайнерское решение. Можно представить себе другой случай, когда именно это и требуется. Вы можете написать свою собственную оболочку islice, которая будет работать так, как вы хотите.

tdelaney 24.06.2024 23:52

@jsbueno - Я бы не согласился с тем, что это побочный эффект. Предположим, у меня есть файловый итератор. islice гарантирует, что я использовал строки до start-1. Это не крайний случай и не проблема исключительно производительности.

tdelaney 24.06.2024 23:56

Действительно, я думаю, что основной вариант использования islice — пропустить первые n элементов. Во всяком случае, я не упоминал об этом в своем ответе.

jsbueno 25.06.2024 00:00

Представьте, что вы вызываете islice() несколько раз для одного и того же итератора. Вам необходимо убедиться, что каждый из них пропускает start элементы, чтобы следующий звонок начался в нужном месте.

Barmar 25.06.2024 00:01

Я предлагаю использовать более простой код и показать его вывод. Я был незнаком со всем этим Mock, и меня это отвлекает, и я подозреваю, что я не одинок.

no comment 25.06.2024 12:41
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
6
90
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Просто реализация не проверяет заранее, больше ли stop, чем start.

Вы можете проверить это перед вызовом islice, в этом случае djust предоставит пустой итератор:

isliceResult = list(itertools.islice(iterable, start, stop, step) if stop >= start) else ())

(выше () обозначает пустой кортеж).

Обратите внимание, что по самой природе islice аргумент step не может быть отрицательным, поэтому условие start <= stop достаточно для всех рабочих случаев. Если вы ожидаете получить действительные отрицательные шаги, то, вероятно, будет проще написать собственный код, чем пытаться повторно использовать islice для этого случая.

Ответ принят как подходящий

Использование элементов базового итератора является частью контракта islice и не может быть оптимизировано. В разделе рецептов официальной документации itertools есть даже рецепт, который основан на использовании пустых фрагментов, потребляющих элементы:

def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is None, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        collections.deque(iterator, maxlen=0)
    else:
        next(islice(iterator, n, n), None)

Однако контракт возник скорее по исторической случайности, чем по факту замысла.

Рэймонд Хеттингер, человек, который разработал функцию и написал оригинальную документацию, не собирался islice давать какие-либо обещания относительно конечного состояния базового итератора - даже обещания, на которые опирается consume рецепт. Цитирую один из постов Рэймонда в разговоре об аналогичном крайнем случае с step:

В настоящее время нет никаких обещаний или гарантий относительно конечного состояния. итератора.

И их следующий пост в этом разговоре:

Я написал инструменты, документацию и тесты. Если вы интерпретируете «обещание» в тексте, могу вас заверить, что оно не было задумано. Поведение не определено, потому что я никогда его не определял. Я рад разъяснить документацию, чтобы сделать это явным.

В результате этого разговора реализация была скорректирована, чтобы изменить поведение в этом крайнем случае, и с этого момента детали потребления элементов считались определенными.

Спустя годы кто-то другой поднял этот start>stop случай. Ответ Раймонда включает следующую строку:

Наличие islice() всегда потребляет как минимум «начальное» количество значений кажется достаточно разумным, и это задокументированное поведение: «Если start не равно нулю, то элементы из итерации пропускаются до тех пор, пока не будет достигнут старт. ... Если stop равен нулю. Нет, то итерация продолжается до тех пор, пока итератор не будет исчерпан, если в противном случае он останавливается в указанной позиции».

указывая, что они считали потребление start предметов в start>stop случае частью документально подтвержденного договора.

Этот аргумент на самом деле не работает, поскольку start==остановимся на этом.

no comment 25.06.2024 11:42

@nocomment: Любой аргумент о том, что использование элементов не является необходимым для start>stop, в равной степени применим и к start==stop.

user2357112 25.06.2024 11:51

Нет, конфликт с цитируемым «останавливается в указанной позиции» — это конфликт только тогда, когда start>stop.

no comment 25.06.2024 11:55

@nocomment: Ах, это то, на чем ты сосредоточен. Поскольку в вопросе потребление элементов представлено как «ненужное» и рассматривается с точки зрения проблемы производительности, мой ответ был сосредоточен на демонстрации того, что потребление элементов является частью контракта islice и не может быть оптимизировано. Полагаю, я могу немного скорректировать ответ.

user2357112 25.06.2024 12:09

Да, вопрос конкретно о том, «когда stop меньше, чем start» (заголовок), поэтому спорить с чем-то, где это не так, довольно бессмысленно. Я сам иногда использовал stop=0, когда start было более длинным выражением, чем просто «n», и я с нетерпением жду действительно убедительного аргумента. Я просмотрел тестовые примеры, поскольку я бы воспринял их как демонстрацию намерения, но я не думаю, что это какое-либо тестирование. Я вижу некоторые с start>stop и один, который потом проверяет состояние итератора, но ни один не тестирует оба.

no comment 25.06.2024 12:18

@nocomment: сейчас я скорректировал свой ответ.

user2357112 25.06.2024 12:57

Теперь выглядит великолепно. Автор вопроса start>stop даже сказал: «Не знаю, случайное это совпадение или намеренное», что очень близко к этому вопросу. Я интерпретировал документ так, как сказал там Рэймонд, хотя я думаю, что это можно понимать по-другому (начальная часть не имеет приоритета над стоповой частью), но Рэймонд ясно дал понять, что это значит. Я просто озадачен, почему он сказал, что изменение islice на отказ от потребления чего-либо «стимулирует новую и странную идиому для «потребления» как islice(it, n, 0)», поскольку тогда это даже не сработает. В любом случае... Стоит ли удалять наши комментарии?

no comment 25.06.2024 15:17

Другие вопросы по теме

Дополнение до определенного уровня и добавление символа в конце
Расширение Python GDB: я пытаюсь получить вызывающую строку и информацию о файле для вызова макроса, но получаю неправильные значения из-за комментариев
Аутентификация ManagedIdentityCredential недоступна. Запрошенный идентификатор не был назначен этому ресурсу
Использование REST API в Python для запуска рабочих процессов в Azure Purview
Как исправить столбец с числовыми значениями, который воспринимается как строковое поле из-за пустых строк в фрейме данных Pandas?
Как оценить цвет изображения Pyplot в данной точке?
Возможно ли иметь встроенное определение класса данных в Python?
Добавьте значения двух Dataframe на основе похожих значений строк
С помощью Python извлеките в файл Excel значение ячейки в строке, где ячейка в той же строке содержит строку символов из XML-файла
Обмен датой начала, датой окончания и другими столбцами с более ранней строкой, если даты больше 8 в фрейме данных pandas