Комментарии 43
Кажутся ли вам циклы, построенные с использованием enumerate(), более читабельными, чем циклы, созданные с использованием range(len())?
Они не эквивалентны в случае, если итерируемый список изменяет длину в процессе итерации поскольку диапазон range фиксируется по длине в начале цикла и потом не меняется.
Менять набор данных на ходу во время забега по нему — это экстремальное развлечение.
Оно хорошо, когда надо оптимизировать производительность, а во всех остальных случаях — плохо.
Для таких случаев есть генераторы. Они как раз для описанных выше случаев.
Я имею в виду вот что:
for item in the_list:
....
the_list += more_data
....
del the_list[from:to:step]
....
Вот это — экстремальное развлечение. Потому что зависит от устройства итератора по списку.
for i in range(len(the_list)):
item = the_list[i]
....
the_list += more_data
....
del the_list[from:to:step]
....
Более предсказуемо (всё на виду), но требует офигенной тщательности и понимания, как изменяется структура в процессе забега.
А если у вас есть отдельно источник, отдельно приёмник — то это ок
for item in the_source:
....
the_receiver.append(processed(item))
....
Но это не "итерируемый список меняет длину".
Надо постараться, чтобы корректно сформулировать требования к контейнеру, у которого есть и итератор, и модификаторы.
Корректно не только в смысле "не упадёт, даже если мы табуретку из-под ног выдернем", а чтобы это было осмысленно.
А главное, — зачем всё это нужно? Одновременно итерироваться по очереди и изменять её?
И нельзя ли выразить то же самое какими-то более прозрачными и очевидными средствами? (циклом while, в конце концов)
На самом деле, даже в JavaScript существуют методы, позволяющие перебирать массивы, так сказать, без посредников. Речь идёт о циклах forEach и for of.
Так в питоне если сделаем так, тоже получается без посредников:
for x in scores:..
И с enumerate() все классно, не думаю, что у кого то к нему претензии, но range() уж точно не нужно так использовать.
for x in scores:..
нельзя обратиться к индексу элемента, а это иногда нужно.Но автор говорит «даже в JavaScript существуют методы, позволяющие перебирать массивы, так сказать, без посредников» и при этом не приводит в пример данный способ, который является «без посредников». Да и в JS, чтобы обратиться к индексу нужно прибегнуть к помощи «посредников», по крайней мере обычного массива, на сколько я знаю
Какие же например в пайтоне использовать циклы для получения индекса?А зачем вообще другой цикл использовать, если благодаря наличию универсальных механизмов enumerate, range, zip, slice и т. д., можно гибче, и при этом не сложнее, получить больше комбинаций применения?
Речь о том, что тот же zip или slice можно использовать не только в циклах. Так зачем запоминать семантику двух разных подходов (for и foreach), когда и циклы, и остальное, можно положить поверх одного for, применяя вспомогательные простые универсальные механизмы, которые касаются не только циклов?
person_names = ["John", "Mike", "Ashurbanipal"]
for person_name in person_names:
do_something(person_name)
имея опыт работы с другим популярным языком программирования, вроде PHP или JavaScript, то вам знакома методика применения переменной-счётчика, хранящей, например, индекс текущего элемента массива, обрабатываемого в цикле.
В PHP самым стандартным является такой подход:
$scores = [54, 67, 48, 99, 27];
foreach ($scores as $k => $score) {
echo "{$k} {$score}\n";
}
Банальнейшая вещь и с незапамятных времён.
Но это, конечно же, не совсем enumerate
, ведь вместо инкремента тут ключи ассоциативного массива (в данном случае не отличающиеся).
А вот сие уже гарантированно эквивалентно:
$scores = [54, 67, 48, 99, 27];
foreach (array_values($scores) as $i => $score) {
echo ($i + 1) . " {$score}\n";
}
Сам enumerate
тоже можно переизобрести, если очень хочется:
function enumerate(array $array, int $start = 0): array
{
$keys = range($start, count($array) - 1 + $start);
return array_combine($keys, $array);
}
$scores = [54, 67, 48, 99, 27];
foreach (enumerate($scores, 1) as $i => $score) {
echo "{$i} {$score}\n";
}
Либо с yield
(медленнее при использовании с массивами (PHP 7.4)):
function enumerate(iterable $iterable, int $start = 0): iterable
{
$i = $start;
foreach ($iterable as $v) {
yield $i++ => $v;
}
}
for i, list in enumerate(scores, 1):
for j, score in enumerate(list, 1):
print(i, j, score)
for x,y, value in array:
Есть такой длинный и уродливый:
for x, y, value in [(x, y, v) for (x, l) in enumerate(array) for (y, v) in enumerate(l)]:
Лучше уж в две строки.
Не чистый Python, но есть такой вариант в numpy:
import numpy as np
array = [[1, 2], [3, 4]]
for (i, j), item in np.ndenumerate(array):
print(f"array[{i}, {j}] = {item}")
array[0, 0] = 1
array[0, 1] = 2
array[1, 0] = 3
array[1, 1] = 4
Также работает с размерностями > 2.
scores = [54,67,48,99,27]
for i in range(len(scores)):
print(i, scores[i])
Проблема этого цикла заключается в том, что он не очень хорошо соответствует идеологии Python. В нём мы не перебираем список, а, вместо этого, используем вспомогательную переменную i для обращения к элементам списка.
Я так и не понял в чем, собственно, проблема? Что если мне нужно организовать цикл на заданное количество шагов, не работая непосредственно со списком? Сколько раз читал про это, но нигде не увидел реально обоснованой причины так не делать. Заявления, что это «непитонично», звучат как пук в банку. Могли бы хоть сравнения замеров быстродействия выложить или детальный разбор механизмов работы.
«Питонистический подход к циклам for: range() и enumerate()»
Автор заметки, перевод которой мы сегодня публикуем, хочет рассказать о некоторых особенностях использования циклов for в Python.
В самой статье же рассматривается только итерация по спискам, хотя это вообще не является прямой задачей цикла «for». Так что тут либо название статьи менять, либо раскрывать тему нормально, а то сейчас впечатлительные начитаются и будут везде писать что «for i in range(len(seq)):» порочно независимо от контекста.
ЗЫ: Не могу влепить статье дизлайк, буду благодарен, если кто-то сделает это за меня.
Автор статьи же затрагивает только «foreach» составляющую, что прямо противоречит названию и аннотации статьи и может ввести людей в заблуждение.
и то что в питоне их обьединили в одно не ставит между ними знак равенства
Это не совсем так, for
в Python только один, и для инкремента числа используется тот же самый обход последовательности, генерируемой функцией range
.
Вот где действительно объединены, так это в C++, Java, JavaScript и (частично) в Perl5.
С++ обычно не рассматривается в отрыве от С, так как является его надмножеством, а в С вполне есть старый добрый «for» без всяких последовательностей. В JS эти два цикла вообще вроде бы разделены друг от друга?
Строго говоря в питоне вообще нет «for» — есть только «foreach». И инкремента индекса (как и самого индекса) тоже нет — есть проход по списку, сгенерированому функцией range() или поданому явно.
Что я и сказал, да.
Строго говоря в питоне вообще нет «for» — есть только «foreach».
Касательно терминологии, есть множество языков, где for
выступает именно как оператор обхода по итератору (условно).
Самый яркий пример – Raku, потому что там есть for @scores.kv -> $i, $score {
и есть loop (my $i = 0; $i < @scores.elems; ++$i) {
.
С++ обычно не рассматривается в отрыве от С, так как является его надмножеством, а в С вполне есть старый добрый «for» без всяких последовательностей.
Почему же не рассматривается? Рассматривается.
Особенно когда обсуждаются специфичные для C++ особенности.
Вот где действительно объединены, так это в C++, <…>
Я имею в виду, что оператор for
используется для обоих вариантов цикла в C++. В C, естественно – нет.
И говоря, что конструкцию вида «for i in range(len(seq)):
» не стоит использовать, нужно уточнять, что это касается исключительно ситуаций итерирования обьекта. Для имитации же именно поведенияfor
эта конструкция вполне легальна.
Я не спорю, и статья явно направлена на тех, кто совсем не в курсе и допускает довольно серьёзные неточности.
с названием одного, а механикой другого.
Моя же позиция в том, что название for
часто используется и для другой механики во многих языках.
Например, в Rust, Kotlin, Julia, Nim, Zig.
Кстати, и в других языках в цикле for не обязательно заранее знать число итераций, условие выхода из цикла может быть довольно произвольным. Так что это совсем не критерий. Вот итерирование по элементам против выхода по условию — это реальная разница между foreach и for.
В питоне решили, что выход из цикла по условию недостаточно красив для этого языка и не стоит специальной конструкции. Хотя если хочется из цикла всегда можно выйти изнутри цикла по условию.
Что касается цикла с выходом по условию, то в питоне есть нужный инструмент и зовется он «while». И он достаточно красив для этого языка.
test_list = [1,2,3,4,5,6]
[print(*ll) for ll in enumerate(list)]
Питонистический подход к циклам for: range() и enumerate()