Рубрики
Без рубрики

Циклы в Python

Автор оригинала: 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 довольно хороша.

Ссылки и ссылки

Признание

Автор хотел бы поблагодарить Герольда Рупрехта и Мэнди Ноймайер за их поддержку и комментарии при подготовке этой статьи.