Автор оригинала: Frank Hofmann.
Выбор правильной конструкции цикла
Python предлагает множество конструкций для выполнения циклов. В этой статье они представлены и даны советы по их конкретному использованию. Кроме того, мы также рассмотрим производительность каждой циклической конструкции в вашем коде Python. Это может быть удивительно для вас.
Петли, Петли, Петли
Язык программирования обычно состоит из нескольких типов базовых элементов, таких как назначения, операторы и циклы. Идея цикла состоит в том, чтобы повторять отдельные действия, которые указаны в теле цикла. Различные виды петель являются общими:
- до тех пор, пока указанное условие истинно (в то время как условие do sth.)
- до тех пор, пока не будет выполнено определенное условие (do sth. до состояния)
- для фиксированного числа шагов (итераций) (для/от ‘x’ до ‘y’ do sth.)
- бесконечный цикл и выход/разрыв по условию (в то время как условие 1 делает sth. и выходите при условии 2)
Циклические конструкции, поддерживаемые Python
Python поддерживает частичное число названных выше конструкций, а также предлагает уникальные расширения для упомянутых нами типов.
Основные циклы while
while condition: statements
Пока выполняется “условие”, все операторы в теле цикла while
выполняются хотя бы один раз. После каждого выполнения операторов выполняется повторная оценка условия. Написание цикла выглядит следующим образом:
Листинг 1
fruits = ["banana", "apple", "orange", "kiwi"] position = 0 while position < len(fruits): print(fruits[position]) position = position + 1 print("reached end of list")
Этот код будет выводить один элемент списка за другим:
banana apple orange kiwi reached end of list
while Циклы с предложением else
Эта конструкция специфична для языка Python, но довольно полезна:
while condition: statements else: statements
Этот цикл while
действует аналогично обычному циклу while
, введенному ранее. Операторы в части else
выполняются, как только условие перестает быть истинным. Например, в случае, если достигнут конец списка, как в нашем предыдущем примере. Вы можете интерпретировать его как тогда
, если условие цикла больше не выполняется.
Листинг 2
fruits = ["banana", "apple", "orange", "kiwi"] position = 0 while position < len(fruits): print(fruits[position]) position = position + 1 else: print("reached end of list")
Это выведет один элемент списка за другим, а также дополнительный текст из оператора print
в предложении else:
banana apple orange kiwi reached end of list
Этот вид цикла с предложением else
удобен для вывода сообщений или выполнения операторов в случае сбоя вашего условия.
Важно отметить, что предложение else
не выполняется, если вы выходите из цикла while
или если ошибка выбрасывается из цикла while
.
Бесконечные циклы while
Бесконечные циклы всегда преподаются как критические компоненты и их следует избегать, если условие разрыва является сложным делом. Хотя есть случаи, когда бесконечные циклы помогают вам писать код элегантным способом.
Вот лишь несколько примеров использования бесконечных циклов:
- устройства, которые пытаются поддерживать сетевые соединения активными, например беспроводные точки доступа
- клиенты, которые пытаются постоянно обмениваться данными с хост-системой, например с сетевой файловой системой (NFS или Samba/CIFS)
- игровые циклы для рисования и обновления вашего игрового состояния
while True: if condition: break statements
Имейте в виду, что операторы в теле бесконечного цикла выполняются по крайней мере один раз. Вот почему я рекомендую записать условие разрыва в качестве самого первого оператора после начала цикла. Следуя нашему примеру кода, бесконечный цикл выглядит следующим образом:
Листинг 3
fruits = ["banana", "apple", "orange", "kiwi"] position = 0 while True: if position >= len(fruits): break print(fruits[position]) position = position + 1 print("reached end of list")
для циклов с итератором
Работа со списками описывается как использование ключевого слова for
в сочетании с итератором. Псевдокод выглядит следующим образом:
for temp_var in sequence: statements
Это упрощает код Python для обработки нашего списка следующим образом:
Листинг 4
fruits = ["banana", "apple", "orange", "kiwi"] for food in fruits: print(food) print("reached end of list")
В этом типе циклической конструкции интерпретатор Python обрабатывает итерацию по списку и заботится о том, чтобы цикл не выходил за пределы диапазона списка. Имейте в виду, что операторы в теле цикла выполняются один раз для каждого элемента в списке – независимо от того, является ли он только одним или двадцатью тысячами.
Если список пуст, операторы в теле цикла не выполняются. Изменение списка с точки зрения добавления или удаления элементов в цикле for
может запутать интерпретатор Python и вызвать проблемы, поэтому будьте осторожны.
для циклов с итератором и предложением else
Подобно циклу while
, Python также предлагает оператор else
для цикла for
. Он работает аналогично и может быть интерпретирован как тогда
, как и раньше. Псевдокод выглядит следующим образом:
for temp_var in sequence: statements else: statements
Используя это ключевое слово, наш код изменяется следующим образом:
Листинг 5
fruits = ["banana", "apple", "orange", "kiwi"] for food in fruits: print(food) else: print("reached end of list")
Неподдерживаемые Циклические Конструкции
Как было сказано в начале, существует много различных стилей циклов. Однако Python не поддерживает их все. Python не поддерживает цикл do-until
или цикл foreach
, как это, возможно, известно из PHP. Такие случаи решаются с помощью оператора Python in
, который создает довольно сексуальный код, если вы с ним знакомы. См. Альтернативные способы написания цикла сверху.
Какую петлю выбрать?
В общем случае циклы while condition
требуют указания условия перед операторами цикла. Это может привести к тому, что операторы в теле цикла никогда не будут выполнены. Кроме того, не всегда ясно, сколько раз цикл будет выполняться для циклов while
. Вместо этого циклы for
фокусируются на итераторе, который определяет, как часто выполняются операторы в теле цикла.
Рекомендуется использовать цикл for
, если вы точно знаете количество элементов, которые будут повторяться. Напротив, цикл while
лучше подходит для тех случаев, когда у вас есть логическое выражение для вычисления, а не список элементов для цикла.
Улучшение качества вашего кода
Многие молодые программисты не всегда заботятся о качестве своего кода, в основном потому, что они выросли в то время, когда никто не должен думать о памяти и мощности процессора – просто у нас их достаточно в современных компьютерах. Вместо этого более опытные (или “старые”) разработчики более склонны максимально оптимизировать свой код и могут запомнить подсчет инструкций процессора и количество используемых ячеек памяти.
Так что же означает качество сегодня? С точки зрения эффективности он охватывает написание как можно меньшего количества кода и эффективное выполнение кода – только столько инструкций процессора, сколько необходимо. Во-первых, с сегодняшними интерпретаторами, временем выполнения и фреймворками это довольно трудно правильно рассчитать, а во-вторых, это всегда компромисс между этими двумя показателями. Ключевые вопросы заключаются в том, как часто будет использоваться этот код и сколько времени мы потратим на его оптимизацию, чтобы выиграть несколько микросекунд процессорного времени.
В качестве примера мы рассмотрим цикл for
, повторяющий список. Обычно мы пишем это следующим образом:
Листинг 6
for entry in range(0, 3): print(entry)
Это выводит значения 0, 1 и 2. Метод range()
создает итерацию [0, 1, 2]
каждый раз, когда голова цикла оценивается. Поэтому лучше написать его следующим образом:
Листинг 7
entryRange = range(0, 3) for entry in entryRange: print(entry)
Хотя для данного примера это может показаться не очень большой оптимизацией, рассмотрим, был ли диапазон от 0 до 1 000 000 или более. По мере того как наш список увеличивается, мы экономим больше времени и наш код выполняется быстрее.
Кроме того, эти утверждения могут быть выражены как цикл while
:
Листинг 8
entryRange = range(0, 3) index = 0 while index < len(entryRange): print(entryRange[index]) index = index + 1
И к этому моменту кажется немного бессмысленным даже использовать функцию range ()
. Вместо этого мы могли бы просто использовать константу для условного и индекса
в качестве счетчика для условного и печати:
index = 0 while index < 3: print(index) index = index + 1
Небольшие оптимизации, подобные этим, могут обеспечить небольшие улучшения производительности для ваших циклов, особенно когда число итераций становится очень большим.
Тесты производительности
До сих пор мы говорили о циклическом коде и о том, как его правильно писать. Тест производительности может помочь пролить некоторый свет. Идея любезно заимствована из интересной статьи в блоге Неда Батчелдера [1].
Используется инструмент perf
, который выполняет тесты производительности для выполняемого программного кода [2]. Основной вызов-это perf stat program
тогда как stat
сокращает статистику, а программа-это вызов, который мы хотели бы оценить. Чтобы проверить наши варианты цикла эти вызовы были сделаны:
Листинг 9
perf stat python3 while-1.py perf stat python3 while-2.py perf stat python3 while-3.py perf stat python3 for-4.py perf stat python3 for-5.py perf stat python3 for-6.py perf stat python3 for-7.py perf stat python3 while-8.py
Эти результаты являются средними на основе 10 запусков из-за различий в нагрузке в ядре Linux. Результаты приведены в следующей таблице:
часы задач (мсек) | 2.016008e+01 | 1.853526e+01 | 1.597539e+01 | 1.542733e+01 | 1.550367e+01 |
контекстные переключатели | 1.000000e+01 | 1.100000e+01 | 1.000000e+01 | 1.300000e+01 | 1.000000e+01 |
миграция ЦП | 0.000000e+00 | 0.000000e+00 | 2.000000e+00 | 1.000000e+00 | 1.000000e+00 |
ошибки страниц | 8.510000e+02 | 8.490000e+02 | 8.550000e+02 | 8.480000e+02 | 8.510000e+02 |
циклы | 4.191501e+07 | 4.493884e+07 | 4.440370e+07 | 4.298339e+07 | 4.248921e+07 |
инструкции | 4.683382e+07 | 4.680319e+07 | 4.692638e+07 | 4.659667e+07 | 4.670135e+07 |
Для Списков 6-8 это выглядит следующим образом:
часы задач (мсек) | 1.648032e+01 | 1.819344e+01 | 1.573463e+01 |
контекстные переключатели | 9.000000e+00 | 1.100000e+01 | 1.100000e+01 |
миграция ЦП | 0.000000e+00 | 0.000000e+00 | 1.000000e+00 |
ошибки страниц | 8.500000e+02 | 8.510000e+02 | 8.530000e+02 |
циклы | 4.242464e+07 | 4.256955e+07 | 4.303884e+07 |
инструкции | 4.670389e+07 | 4.672419e+07 | 4.669571e+07 |
Вывод
Python предлагает различные способы повторения действий и написания циклов записи. Существуют варианты для каждого конкретного случая использования. Наши тесты показали, что циклы находятся в одном измерении с небольшими различиями, и оптимизация интерпретатора Python довольно хороша.
Ссылки и ссылки
- [1] Ned Batchelder: How many instructions in a print statement?, July 2013
- [2] Пакет Debian linux-perf
Признание
Автор хотел бы поблагодарить Герольда Рупрехта и Мэнди Ноймайер за их поддержку и комментарии при подготовке этой статьи.