Цикл for в одну строку

Немного информатики

Как было отмечено выше,

Напишем на псевдокоде классическую схему:

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

Циклы, как механизм программирования, нужны, главным образом, для упрощения написания кода. Вполне очевидно, что создавать программу, выполняющую определённую операцию для каждой точки 4К дисплея в отсутствии циклов – это вручную повторять описание нужной команды 4096*2160 раз. Много? Безусловно.

Применение в этой задаче всего одного цикла позволит сократить длину кода, как минимум, на 6 порядков. А если представить, что ту же самую программу нужно переписать для 8К монитора, то, вместо изменения всего одной инструкции в счетчике цикла, вам придётся дописывать ещё пару десятков миллионов строк кода, что является попросту недопустимым по своей величине и трудозатратам объёмом.

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

Функция range()

Теперь пришло время познакомиться со встроенной в Python функцией range(). «Range» переводится как «диапазон». Она может принимать один, два или три аргумента. Их назначение такое же как у функции randrange() из модуля random. Если задан только один, то генерируются числа от 0 до указанного числа, не включая его. Если заданы два, то числа генерируются от первого до второго, не включая его. Если заданы три, то третье число – это шаг.

Однако, в отличие от randrange(), функция range() генерирует не одно случайное число в указанном диапазоне. Она вообще не генерирует случайные числа. Она генерирует последовательность чисел в указанном диапазоне. Так, range(5, 11) сгенерирует последовательность 5, 6, 7, 8, 9, 10. Однако это будет не структура данных типа «список». Функция range() производит объекты своего класса – диапазоны:

>>> a = range(-10, 10)
>>> a
range(-10, 10)
>>> type(a)
<class 'range'>

Несмотря на то, что мы не видим последовательности чисел, она есть, и мы можем обращаться к ее элементам:

>>> a
-10
>>> a5
-5
>>> a15
5
>>> a-1
9

Хотя изменять их нельзя, так как, в отличие от списков, объекты range() относятся к группе неизменяемых:

>>> a10 = 100
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'range' object does not support item assignment

While loop

loop repeats
the sequence of actions many times until some condition evaluates to .
The condition is given before the loop body and is checked before each execution of the loop body.
Typically, the loop is used when it is impossible
to determine the exact number of loop iterations in advance.

The syntax of the loop in the simplest case looks like this:

while some condition:
    a block of statements

Python firstly checks the condition.
If it is False, then the loop is terminated and control
is passed to the next statement after the loop body.
If the condition is True, then the loop body is executed, and then the condition
is checked again.
This continues while the condition is True.
Once the condition becomes False, the loop terminates and
control is passed to the next statement after the loop.

For example, the following program fragment prints
the squares of all integers from 1 to 10. Here one can replace the «while» loop by the
loop:

1
4
9
16
25
36
49
64
81
100
i = 1
while i <= 10:
    print(i ** 2)
    i += 1

In this example, the variable inside the loop iterates from 1 to 10.
Such a variable whose value changes with each new loop iteration
is called a counter. Note that after executing this fragment
the value of the variable is defined and is equal to ,
because when the condition is False for the first time.

Here is another example use of the loop
to determine the number of digits of an integer :

5678
n = int(input())
length = 0
while n > 0:
    n //= 10  # this is equivalent to n = n // 10
    length += 1
print(length)  # 4

On each iteration we cut the last digit of the number
using integer division by 10 (). In the variable
we count how many times we did that.

In Python there is another, easier way to solve this problem:
.

Профилирование эффективности генератора

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

Вы можете сделать это с помощью вызова функции :

import sys
nums_squared_lc = 
sys.getsizeof(nums_squared_lc)
87624
nums_squared_gc = (i ** 2 for i in range(10000))
print(sys.getsizeof(nums_squared_gc))
120

В этом случае размер списка, полученного с помощью выражения составляет 87 624 байта, а размер генератора — только 120. То есть, список занимает памяти в 700 раз больше, чем генератор! Однако нужно помнить одну вещь. Если размер списка меньше доступной памяти на работающей машине, тогда обработка его будет занимать меньше времени, чем аналогичная обработка генератора. Чтобы удостовериться в этом, давайте просуммируем результаты приведенных выше выражений. Вы можете использовать для анализа функцию :

import cProfile
cProfile.run('sum()')
         5 function calls in 0.001 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001    0.001    0.001 <string>:1(<listcomp>)
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)
        1    0.000    0.000    0.001    0.001 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.sum}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


cProfile.run('sum((i * 2 for i in range(10000)))')
         10005 function calls in 0.003 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    10001    0.002    0.000    0.002    0.000 <string>:1(<genexpr>)
        1    0.000    0.000    0.003    0.003 <string>:1(<module>)
        1    0.000    0.000    0.003    0.003 {built-in method builtins.exec}
        1    0.001    0.001    0.003    0.003 {built-in method builtins.sum}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

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

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

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

Проходите тест по Python и поймите, готовы ли вы идти на курсы

Nested while Loops#

In general, Python control structures can be nested within one another. For example, // conditional statements can be nested:

Similarly, a loop can be contained within another loop, as shown here:

>>>

A or statement found within nested loops applies to the nearest enclosing loop:

Additionally, loops can be nested inside // statements, and vice versa:

In fact, all the Python control structures can be intermingled with one another to whatever extent you need. That is as it should be. Imagine how frustrating it would be if there were unexpected restrictions like “A loop can’t be contained within an statement” or “ loops can only be nested inside one another at most four deep.” You’d have a very difficult time remembering them all.

Seemingly arbitrary numeric or logical limitations are considered a sign of poor program language design. Happily, you won’t find many in Python.

Пример использования цикла while

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

Примечание: Чтобы лучше понять, как работает такая программа, ознакомьтесь со статьями

  • Условные операторы в Python 3
  • Преобразование типов данных в Python 3

Создайте в редакторе файл guess.py. Смысл игры состоит в том, что компьютер загадывает случайное число, которое должен угадать пользователь. Для этого нужно импортировать модуль random с помощью выражения import. Больше информации о пакете можно найти здесь.

Добавьте в файл:

Теперь нужно присвоить переменной number случайное число в диапазоне 1-25 включительно.

Далее нужно добавить цикл while. Сначала инициируйте переменную, а затем создайте цикл.

Переменной number_of_guesses присвоено значение 0, которое будет увеличиваться с каждой попыткой угадать число; это необходимо, чтобы программа не попала в бесконечный цикл. Далее следует выражение while, которое ограничивает number_of_guesses до 5. После пятой неудачной попытки пользователь вернется в командную строку. На данный момент программа принимает только целые числа; в противном случае она выдаёт сообщение об ошибке.

В теле цикла есть выражение print(), которое предлагает пользователю ввести число, которое передаётся функции input() и присваивается переменной guess. После этого строка из переменной guess преобразовывается в целое число. До выхода из цикла нужно ещё увеличить значение number_of_guesses на 1. Максимальное количество попыток – 5.

Затем нужно добавить выражение if, чтобы сравнить значение guess (число, которое ввёл пользователь) со значением number (число, которое загадал компьютер). Если пользователь угадал загаданное число, программа переходит к выражению break, чтобы прервать цикл.

Программа готова к работе. Сохраните и запустите её:

Программа работает, но не сообщает пользователю никаких результатов: пользователь не знает, угадал он число или нет. На данный момент вывод программы выглядит так:

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

Теперь программа сообщит пользователю, угадал он число или нет, но это произойдёт только после того, как он потратит все попытки.

Чтобы исправить это, добавьте ещё несколько условных выражений, теперь в тело цикла while. Так программа сможет сообщить пользователю, что его число меньше или больше загаданного, и это поможет угадать число быстрее.

Сохраните и запустите программу:

Теперь программа помогает пользователю угадать число, даёт ему подсказки. К примеру, если компьютер загадал число 12, а пользователь ввёл 19, программа подскажет, что введённое число больше загаданного.

Эту игру можно усовершенствовать, например, добавить обработку ошибок.

The importance of real code

During the development of this PEP many people (supporters and critics
both) have had a tendency to focus on toy examples on the one hand,
and on overly complex examples on the other.

The danger of toy examples is twofold: they are often too abstract to
make anyone go «ooh, that’s compelling», and they are easily refuted
with «I would never write it that way anyway».

The danger of overly complex examples is that they provide a
convenient strawman for critics of the proposal to shoot down («that’s
obfuscated»).

Yet there is some use for both extremely simple and extremely complex
examples: they are helpful to clarify the intended semantics.
Therefore there will be some of each below.

However, in order to be compelling, examples should be rooted in
real code, i.e. code that was written without any thought of this PEP,
as part of a useful application, however large or small. Tim Peters
has been extremely helpful by going over his own personal code
repository and picking examples of code he had written that (in his
view) would have been clearer if rewritten with (sparing) use of
assignment expressions. His conclusion: the current proposal would
have allowed a modest but clear improvement in quite a few bits of
code.

Another use of real code is to observe indirectly how much value
programmers place on compactness. Guido van Rossum searched through a
Dropbox code base and discovered some evidence that programmers value
writing fewer lines over shorter lines.

Case in point: Guido found several examples where a programmer
repeated a subexpression, slowing down the program, in order to save
one line of code, e.g. instead of writing:

match = re.match(data)
group = match.group(1) if match else None

they would write:

group = re.match(data).group(1) if re.match(data) else None

Another example illustrates that programmers sometimes do more work to
save an extra level of indentation:

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None

This code tries to match pattern2 even if pattern1 has a match
(in which case the match on pattern2 is never used). The more
efficient rewrite would have been:

Итерации

  • Итерация (Iteration) – это одно из повторений цикла (один шаг или один «виток» циклического процесса). К примеру цикл из 3-х повторений можно представить как 3 итерации.
  • Итерируемый объект (Iterable) – объект, который можно повторять. Проще говоря это объект, который умеет отдавать по одному результату за каждую итерацию.
  • Итератор (iterator) – итерируемый объект, в рамках которого реализован метод __next__, позволяющий получать следующий элемент.

Чтобы выполнить итерацию, Python делает следующее:

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

Схема работы цикла «for» в Python

Пример создания итерируемого объекта
Для того чтобы создать собственный класс итерируемого объекта, нужно всего лишь внутри него реализовать два метода: __iter__() и __next__()

  • внутри метода __next__ () описывается процедура возврата следующего доступного элемента;
  • метод __iter__() возвращает сам объект, что даёт возможность использовать его, например, в циклах с поэлементным перебором.

Создадим простой строковый итератор, который на каждой итерации, при получении следующего элемента (т.е. символа), приводит его к верхнему регистру:

Разбор цикла for

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

Мы будем использовать тот же простой цикл for, который мы рассматривали до сих пор. Запишем следующий цикл for в файл for_loop.py.

for word in :
  print(word)
else:
  print("See you later!")

Теперь мы можем получить читаемую форму байт-кода, вызвав dis.dismethod. Запустим следующую команду в терминале.

$ python3 -m dis for_loop.py
  1           0 SETUP_LOOP              28 (to 30)
              2 LOAD_CONST               0 (('You', 'are', 'awesome!'))
              4 GET_ITER
        >>    6 FOR_ITER                12 (to 20)
              8 STORE_NAME               0 (word)

  2          10 LOAD_NAME                1 (print)
             12 LOAD_NAME                0 (word)
             14 CALL_FUNCTION            1
             16 POP_TOP
             18 JUMP_ABSOLUTE            6
        >>   20 POP_BLOCK

  4          22 LOAD_NAME                1 (print)
             24 LOAD_CONST               1 ('See you later!')
             26 CALL_FUNCTION            1
             28 POP_TOP
        >>   30 LOAD_CONST               2 (None)
             32 RETURN_VALUE

Каждый из столбцов в разобранном виде представляет следующее:

  1. Колонка 1: номер строки кода.
  2. Колонка 2: знак «>>», если инструкция является целью перехода.
  3. Колонка 3: смещение байт кода в байтах.
  4. Колонка 4: инструкция байт-кода.
  5. Колонка 5: аргументы инструкции. В скобках отображается более понятный для человека имя аргументов.

Теперь давайте шаг за шагом пройдемся по нашему разобранному байт-коду и попытаемся понять, что на самом деле происходит.В этом описание термин TOS означает вершина стека (top of the stack)

строка 1, “for word in :” переводится как:0 SETUP_LOOP 28 (to 30)Этот оператор помещает блок для цикла for в стек. Блок занимает от этой инструкции до 28 байт, то есть до «30»
Это означает, что если в цикле for есть оператор break, управление переместится на «30» байт

Обратите внимание, блок else, будет пропущен если встретится оператор break. 2 LOAD_CONST 0 ((‘You’, ‘are’, ‘awesome!’))
4 GET_ITER
6 FOR_ITER 12 (to 20)Эта инструкция получает TOS, который на данный момент является нашим итератором, и вызывает для него метод next()

Если next() возвращает значение, оно помещается в стек, и будет выполнена следующая инструкция «8 STORE_NAME».
Как только функция next() указывает, что итератор исчерпан (т. к. сработал StopItered), TOS (а именно итератор) будет извлечен из стека, а счетчик байтового кода будет увеличен на 12. Это означает, что элемент управления перейдет к инструкция «20 POP_BLOCK».

8 STORE_NAME 0 (word)

строка 2, “print(word)” переводится как:10 LOAD_NAME 1 (print)
12 LOAD_NAME 0 (word)
14 CALL_FUNCTION 1Это команда вызывает функцию с позиционными аргументами.
Аргументы, связанные с функцией, будут присутствовать в TOS, как мы видели в предыдущей инструкции. Все аргументы выталкиваются до тех пор, пока не получит вызываемый объект, то есть print.
Как только он получает вызываемый объект, он вызывается путем передачи ему всех аргументов.
Как только вызов выполнен, его возвращаемое значение будет передано в TOS. В текущий момент это будет None.

16 POP_TOP
18 JUMP_ABSOLUTE 6Счетчик байт-кода теперь установлен на «6». Это означает, что следующая выполняемая инструкция будет «6 FOR_ITER». Вот так цикл проходит по элементам итератора.
Обратите внимание, что инструкция «6 FOR_ITER» заставит программу выйти из этого цикла и перейти к «20 POP_BLOCK», как только все элементы итератора будут исчерпаны.

20 POP_BLOCK

Обратите внимание, что номер строки 3, т.е., else, не имеет каких-либо конкретных инструкций, связанных с этим. Управление программой естественным образом переходит к следующей инструкции, которая в основном состоит из операторов, связанных с else.
строка 4, “print(“See you later!”)” переводится как:22 LOAD_NAME 1 (print)
24 LOAD_CONST 1 (‘See you later!’)
26 CALL_FUNCTION 1
28 POP_TOP

Следующие две инструкции в основном загружают возвращаемое значение нашего скрипта (None) в стек и возвращают его.30 LOAD_CONST 2 (None)
32 RETURN_VALUE

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

Итерируемые объекты (iterables) и итераторы (iterators)

Итерируемые объекты

В предыдущем разделе мы использовали термин «iterables» для обозначения объекта, который итерировался циклом for. Теперь давайте попробуем понять, что такое итерируемый объект в Python.

В Python итерируемый объект — это любой объект, который можно использовать в итерации с использованием цикла for. Это означает, что объект должен возвращать итератор при передаче в метод iter(). Давайте посмотрим примеры некоторых часто используемых встроенных итерируемых объектов в Python.

>>> iter("You are awesome!") # String
<str_iterator object at 0x1041ad2e8>
>>> iter() # List
<list_iterator object at 0x1041ad358>
>>> iter(("You", "are", "awesome!")) # Tuple
<tuple_iterator object at 0x1041ad390>
>>> iter({"You", "are", "awesome!"}) # Set
<set_iterator object at 0x1041ac678>
>>> iter({1: "You", 2: "are", 3: "awesome!"}) # Dictionary
<dict_keyiterator object at 0x10400df48>
>>> iter(range(3)) # Range function
<range_iterator object at 0x1041a1450>

Как вы можете видеть, когда мы вызываем iter() для итерируемого объекта, он возвращает объект итератора.

Итераторы

А что такое итератор? В Python итератор определяется как объект, представляющий поток данных. По сути, если мы передаем итератор во встроенный метод next(), он должен вернуть следующее значение из связанного потока данных. Когда все элементы исчерпаны, должно появиться исключение StopIteration. Он должен продолжать вызывать исключение StopIteration для любых последующих вызовов метода next().

Примеры итератора со списком.

>>> my_list = 
>>>
>>> # Get the iterator.
... list_iterator = iter(my_list)
>>>
>>> # Get next element of iterator.
... next(list_iterator)
'You'
>>> next(list_iterator)
'are'
>>> next(list_iterator)
'awesome!'
>>> next(list_iterator)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> next(list_iterator)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Итераторы тоже итеративные объекты! Но..

Следует помнить одну интересную вещь: итераторы сами по себе также поддерживают (обязаны поддерживать согласно протоколу итератора) метод iter(). Это означает, что мы можем вызвать метод iter() для итератора и получить сам объект итератора.

>>> my_list = 
>>> list_iterator = iter(my_list)
>>> list_iterator
<list_iterator object at 0x1099a6320>
>>> iterator_of_iterator = iter(list_iterator)
>>> iterator_of_iterator
<list_iterator object at 0x1099a6320>

Таким образом, мы можем использовать итераторы везде, где ожидается итерация, например, в цикле for.

Однако обратите внимание, что вызов iter() для объекта-контейнера, такого как list, каждый раз будет возвращать новый итератор. Но вызов iter() для итератора просто возвращает тот же объект

>>> my_list = 
>>> iter(my_list)
<list_iterator object at 0x1099a62b0>
>>> iter(my_list) # This gives a fresh iterator object
<list_iterator object at 0x1099a62e8>
>>> my_list = 
>>> list_iter = iter(my_list)
>>> list_iter
<list_iterator object at 0x1099a62b0>
>>> iter(list_iter) # This returns the same iterator object
<list_iterator object at 0x1099a62b0>
>>> iter(list_iter) # This returns the same iterator object
<list_iterator object at 0x1099a62b0>

Итерация по списку дважды

Обратите внимание, что это работает так, как мы ожидали

>>> my_list = 
>>>
>>> for word in my_list:
...   print(word)
...
You are Awesome!
>>> for word in my_list:
...   print(word)
...
You are Awesome!

Итерация через list_iterator дважды

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

>>> my_list = 
>>> list_iterator = iter(my_list)
>>>
>>> for word in list_iterator:
...   print(word)
...
You are Awesome!
>>>
>>> for word in list_iterator:
...   print(word)
...
>>>
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector