Программирование и научные вычисления на языке Python/§3

В следующих двух уроках мы познакомимся с основными конструкциями программирования: циклами while и for, if-else-ветвлениями и функциями, определяемыми самими пользователями. Все это будет использоваться практически в любой программе. Цель нашей следующей программы — развить наш предыдущий пример о двух температурных шкалах и напечатать на экране таблицу с градусами Цельсия в первом столбце и соответствующими им градусами Фаренгейта во втором столбце:

-20    -4.0
-15     5.0
-10    14.0
 -5    23.0
  0    32.0
  5    41.0
 10    50.0
 15    59.0
 20    68.0
 25    77.0
 30    86.0
 35    95.0
 40   104.0

Наивный подход

Конечно, мы можем повторить некоторый код столько раз, сколько нужно с соответствующими значениями переменной, например:

C = -20;  F = 9.0/5*C + 32; print C, F
C = -15;  F = 9.0/5*C + 32; print C, F
C = -10;  F = 9.0/5*C + 32; print C, F
C =  -5;  F = 9.0/5*C + 32; print C, F
C =   0;  F = 9.0/5*C + 32; print C, F
C =   5;  F = 9.0/5*C + 32; print C, F
C =  10;  F = 9.0/5*C + 32; print C, F
C =  15;  F = 9.0/5*C + 32; print C, F
C =  20;  F = 9.0/5*C + 32; print C, F
C =  25;  F = 9.0/5*C + 32; print C, F
C =  30;  F = 9.0/5*C + 32; print C, F
C =  35;  F = 9.0/5*C + 32; print C, F
C =  40;  F = 9.0/5*C + 32; print C, F

и после запуска получить:

-20 -4.0
-15 5.0
-10 14.0
-5 23.0
0 32.0
5 41.0
10 50.0
15 59.0
20 68.0
25 77.0
30 86.0
35 95.0
40 104.0

Форматирование выглядит криво, но его собственно как такового и не осуществлялось и ситуацию легко изменить с помощью printf-форматирования, что мы покажем далее. Но основная проблема заключается в другом — в том, что нам приходится множество раз повторять идентичные выражения, в то время как компьютер создан как раз для автоматизации скучной работы. В общем, нам пора узнать о циклах (loops) и их двух реализациях в Python: циклах while и циклах for.

Цикл while

Цикл while используется для того, чтобы повторять заданные наборы инструкций столько раз, сколько условие сохраняет истину. Проиллюстрируем это на примере. Задача состоит в создании строк таблицы из значений C и F. Первое значение C равно -20 и с шагом 5 оно движется в сторону роста С пока С ≤ 40. Для каждого C считается соответствующее значение F и так записываются две температуры. Записать все сказанное словами в виде кода можно, например, так:


print '------------------' # верхняя линия таблицы
C = -20                    # начальное значение C
dC = 5                     # инкремент для C в цикле
while C <= 40:             # заголовок цикла с условием
    F = (9.0/5)*C + 32     # 1ая инструкция цикла
    print C, F             # 2ая инструкция цикла
    C = C + dC             # 3ья инструкция цикла
print '------------------' # нижняя линия (после цикла)


Уже сейчас стоит рассмотреть в прямом смысле слова незаметную, но очень важную часть языка Python — отступы. Как мы видим, после того как мы объявили блок while, задали условие и поставили двоеточие, весь следующий текст записан с определенным отступом. Ширина этого отступа, которой следует придерживаться, равна четырем пробелам.

Многие начинающие программисты Python забывают ставить двоеточие в конце строки с while — это двоеточие существенно и показывает, что начинается блок инструкций цикла. Немногим позже мы увидим, что имеется множество схожих конструкций, открывающихся двоеточием.

Программисты должны полностью понимать, что происходит в программе и быть готовы дать тот же ответ, что и программа, еще до того как она запущена. Давайте обсудим, что происходит в этом коде. Вначале, мы определяем начальное значение температуры в градусах Цельсия: С = -20. Также мы определяем инкремент dC, чтобы добавлять его к C в цикле. Затем мы начинаем цикл с условием С ≤ 40. В первый момент C = -20, -20 ≤ 40, условие выполняется. Пока условие выполняется, цикл выполняет заложенные в нем действия. То есть считает соответствующее F, печатает две температуры и добавляет dC к C.

После этого совершается второй проход по циклу. Вначале мы проверяем правило цикла: теперь С = -15, но выражение все еще остается истинным: -15 ≤ 40. Поэтому выполняются все те же инструкции, следующие после двоеточия. И так до тех пор пока выражение С ≤ 40 истинно. А ложным оно может стать лишь тогда, когда С > 40.


Немного об инкременте

Новичков в программировании иногда смущает следующее выражение:

C = C + dC

Эта строка выглядит некорректно с точки зрения математики, но как программный код не вызывает никаких нареканий, поскольку вначале происходят какие-то действия с правой стороны от знака равенства, а потом уже они присваиваются переменной слева. В нашем случае складываются C и dC, два объекта типа int. Эта операция дает тоже объект типа int и уже он присваивается переменной C. А объект, связанный с этим именем ранее в памяти освобождается, поскольку на него более не ссылается ни одно имя.

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


C += dC  # то же, что C = C + dC
C -= dC  # то же, что C = C - dC
C *= dC  # то же, что C = C * dC
C /= dC  # то же, что C = C / dC


Логические выражения

В нашем примере с циклом while мы использовали условие записанное как C <= 40, которое принимает значения True (истина) или False (ложь). В зависимости от задачи, вы можете использовать и другие условия проверки:


C == 40  # C равно 40
C != 40  # C не равно 40
C >= 40  # C больше или равно 40
C >  40  # C больше 40
C <  40  # C меньше 40


Сравниваться могут не только числа. Возможно любое логическое (булево) сравнение. Также перед выражением может быть поставлено слово not для изменения True на False и наоборот. Также возможно комбинировать булеву логику, как например:


while x > 0 and y <= 1:
    print x, y


В этом примере, пока оба выражения истинны, будут печататься x, y. В любом обратном случае цикл завершается. Эта функция называется "логическое и". Более "мягкий" подход - "логическое или" (or), для которого выражение принимает истинное значение, когда хотя бы одно из сравнений дает истину:


while x > 0 or y <= 1:
    print x, y


Вот несколько примеров в интерактивном режиме, чтобы потренироваться на логических выражениях и быть с ними более аккуратными, чем это бывает обычно:


>>> x = 0; y = 1.2
>>> x >= 0 and y < 1
False
>>> x >= 0 or y < 1
True
>>> x > 0 or y > 1
True
>>> x > 0 or not y > 1
False
>>> -1 < x <= 0   # -1 < x and x <= 0
True
>>> not (x > 0 or y > 0)
False

Списки

До этого момента все наши переменные могли содержать только одно число, будь оно хоть целым (int), хоть с плавающей точкой (float), хоть комплексным (complex) объектом. Но иногда числа естественным образом группируются. Например, градусы шкалы Цельсия в первом столбце представляют собой группу. Для групп в Python есть специальный тип объектов — list (список), который позволяет организовывать эти группы в последовательности. При этом, с помощью списков мы можем работать как с группой целиком, так и с членами этой группы по отдельности. При этом список может состоять из объектов разного типа, в том числе и из самих списков, что как мы увидим далее, может быть очень удобным.

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


C = [-20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40]


Есть, кстати, и более элегантный способ, но не будем забегать вперед. Теперь наша переменная C ссылается на объект типа list, который содержит последовательность 13 элементов. Все элементы в этом случае являются int-объектами.

Любой элемент в списке связан со своим индексом, который определяет позицию элемента в списке. Первый элемент имеет индекс 0, второй элемент — индекс 1 и так далее. Таким образом в списке C имеется 13 элементов с индексами от 0 до 12. Для того, чтобы вызвать какой-то элемент списка, достаточно вызвать его по индексу, например C[3], если мы хотим вызывать -5.

Элементы в списках можно удалять, добавлять, изменять, вставлять и так далее. Делается это с помощью методов. Обратите внимание на эту концепцию, она встретится нам не раз. Методы определяются через точку. Например метод append для нашего списка C запишется как C.append(v), и этот метод добавит элемент v в конец списка С. А метод C.insert(i,v) вставит новый элемент v на позицию i. В интерактивном режиме выглядит это так:


>>> C = [-10, -5, 0, 5, 10, 15, 20, 25, 30]     # создаем список
>>> C.append(35)              # добавляем в конец новый элемент
>>> C                         # смотрим список
[-10, -5, 0, 5, 10, 15, 20, 25, 30, 35]
>>> C = C + [40, 45]          # способ соединения списков
>>> C
[-10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45]


Но заметим, что, как мы уже знаем, в последнем случае был создан новый список. Сами элементы можно было вставить куда угодно, как мы сейчас сделаем с помощью метода C.insert():


>>> C.insert(0, -15)          # вставить новый элемент -15 с индексом 0
>>> C
[-15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45]


С помощью del C[i] мы можем удалять любые элементы, с помощью len(C) считать число элементов. Метод C.index(v) позволяет определить под каким индексом расположен интересующий нас элемент.


>>> del C[2]                  # удалить третий элемент
>>> C
[-15, -10, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45]
>>> del C[2]                  # удалить то, что теперь третий элемент
>>> C
[-15, -10, 5, 10, 15, 20, 25, 30, 35, 40, 45]
>>> len(C)                    # длина списка
11
>>> C.index(10)
3


Для того, чтобы просто узнать имеется ли какой-то элемент в списке используется команда in:


>>> 10 in C  # есть ли среди элементов C число 10?
True


Кроме всего прочего Python поддерживает отрицательные индексы. Например, в списке C последний элемент может быть обозначен как С[-1], предпоследний как C[-2] и так далее:


>>>  C[-1]
45
>>>  C[-2]
40


Также с помощью списков можно сделать компактное присвоение элементов переменным. Например:


>>> somelist  =  ['book.tex', 'book.log', 'book.pdf']
>>> texfile, logfile, pdf = somelist
>>> texfile
'book.tex'
>>> logfile
'book.log'
>>> pdf
'book.pdf'


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

Заметим, напоследок, различие в функциях и методах. Например, C.append(e) является методом, а len(C) - функцией. Как можно заметить просто визуально, методы записываются через точку. Эта их "префиксность" показывает, что они относятся только к объекту такого типа и производятся только над конкретным экземпляром. Функции же как правило, могут быть использованы для различных типов объектов. При этом каких-то особенных различий в действиях нет. То есть методы по сути те же функции, но привязанные к объекту.

Цикл for

Обычно действия, которые мы производим над одним элементом какой-то последовательности, мы хотели бы произвести и над всеми элементами. Для этого в языках программирования имеется специальная конструкция, называемая цикл for (for loop). Давайте сразу начнем с примера:


degrees = [0, 10, 20, 40, 100]
for C in degrees:
    print 'list  element: ', C
print 'The degrees list has', len(degrees), 'elements'


Предложение for C in degrees создает цикл для всех элементов списка degrees. При каждом проходе по циклу переменная C действует, как курсор: берет соответствующий элемент из списка degrees, начиная с degrees[0] и подставляет в инструкцию цикла. Потом то же делает с degrees[1] и так далее до degrees[n-1], где n — число элементов в списке, которое мы можем определить функцией len(degrees), что и делаем по окончании цикла.

Заголовок цикла for аналогично заголовку цикла while заканчивается двоеточием, после которого следует тело цикла с отступами в 4 пробела. В данном случае в теле цикла всего одна инструкция.

В итоге на экране видим:


list element: 0
list element: 10
list element: 20
list element: 40
list element: 100
The degrees list has 5 elements


Делаем таблицу

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


Cdegrees = [-20, -15, -10, -5, 0, 5, 10, 15, 20,  25, 30, 35, 40]
for C in Cdegrees:
    F = (9.0 / 5) * C + 32
    print C, F


Инструкция print C, F просто печатает значения C и F, а мы бы хотели создать форматированный вывод. Тогда, используя printf-форматирование, запишем окончательный вариант:


Cdegrees = [-20, -15, -10, -5, 0, 5, 10, 15, 20,  25, 30, 35, 40]
print '       C     F'
for C in Cdegrees:
    F = (9.0 / 5) * C + 32
    print '%5d  %5.1f' % (C, F)

Альтернативные возможности

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

while как for

Любой for цикл может быть записан как цикл while. В общем случае for-цикл:


for element in somelist:
    <process element>


может быть преобразован в while:


index = 0
while index < len(somelist):
   element = somelist[index]
   <process element>
   index += 1


В том числе и наш пример:

Cdegrees = [-20, -15, -10, -5, 0, 5, 10, 15, 20,  25, 30, 35, 40]
index = 0
print '       C     F'
while index < len(Cdegrees):
    C = Cdegrees[index]
    F = (9.0 / 5) * C + 32
    print '%5d  %5.1f' % (C, F)
    index += 1

Столбцы в виде списков

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

Cdegrees = [-20, -15, -10, -5, 0, 5, 10, 15, 20,  25, 30, 35, 40]
Fdegrees = []
for C in Cdegrees:
    F = (9.0 / 5) * C + 32
    Fdegrees.append(F)


И если мы после этого распечатаем список Fdegrees, то получим:

[-4.0, 5.0, 14.0, 23.0, 32.0, 41.0, 50.0, 59.0, 68.0, 77.0, 86.0, 95.0, 104.0]

Циклы со списком индексов

Вместо цикла for по заданным элементам списка мы можем использовать цикл for с генерированным списком индексов. Для этого в Python есть функция range, возвращающая список по порядку идущих чисел:

  • range(n) возвращает список [0, 1, 2, ..., n-1]
  • range(start, stop, step) возвращает список [start, start+step, start+2*step, ..., start+(n-1)*step], то есть число stop не включается в список, как и ранее. Например, range(2, 8, 3) дает [2, 5]; range(1, 11, 2) возвращает [1, 3, 5, 7, 9].
  • range(start, stop) то же самое, что range(start, stop, 1).

Таким образом, рассмотренный цикл for может быть записан и так:


Cdegrees = range(-20, 45, 5)
Fdegrees = [0.0]*len(Cdegrees) # list of  0.0  values
for i in range(len(Cdegrees)):
    Fdegrees[i] = (9.0/5)*Cdegrees[i] + 32


Заметим, что в этом примере мы инициализировали Fdegrees списком длиной len(Cdegrees), в котором все элементы равны нулю. Иначе, если бы мы создали пустой список Fdegrees = [], обращение Fdegrees[i] дало бы ошибку, поскольку список пуст, и мест нет.


Еще о списках

Изменение

Сейчас мы могли бы предложить два способа того как можно изменять элементы списка, например:


for c in Cdegrees:
    c += 5


и этот цикл ничего не изменяет, в то время как


for i in range(len(Cdegrees)):
    Cdegrees[i] += 5


работает исправно. Что же не так с первым циклом? Проблема в том, что с в первом случае это всего лишь переменная, которой было присвоено значение элемента списка, и когда мы к этой переменной применяем выражение c += 5, мы изменяем переменную, но не список. То есть, как говорилось ранее, можно представлять такого рода переменные как курсоры, а действие над указателем не изменяет объект. Показать, что происходит, можно на примере первых двух проходов по циклу:


c = Cdegrees[0]
c += 5
c = Cdegrees[1]
c += 5


Как видно отсюда, переменная c может быть использована только для чтения элементов, но не для их изменения. Только присваивание вида:


Cdegrees[i]  =  ...


изменяет элемент списка. Существует возможность совмещения действий с индексами и элементами, например, цикл


for  i, c in enumerate(Cdegrees):
    Cdegrees[i] = c + 5


добавляет число 5 к каждому элементу списка.

Генерация

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


newlist = [E(e) for e in list]


где E(e) представляет собой выражение с элементом e. Вот три примера:


Cdegrees = [-5 + i*0.5 for i in range(n)]
Fdegrees = [(9.0/5)*C + 32 for C in Cdegrees]
C_plus_5 = [C+5 for C in Cdegrees]


Двойной проход (zip)

Мы можем использовать списки Cdegrees и Fdegrees для того, чтобы получить таблицу. Для этого мы должны пройти по обеим последовательностям. Конструкция for element in list в этом случае уже не очень удобна, поскольку она извлекает элементы только из одного списка. Поскольку мы знаем, что в каждом списке одно и то же число элементов, можно поступить например так:


for i in range(len(Cdegrees)):
    print '%5d %5.1f' % (Cdegrees[i], Fdegrees[i])


Такая задача — о том как одновременно пройти по двум и более спискам встречается довольно часто. В Python для этого есть свое элегантное решение:


for e1, e2, e3, ... in zip(list1, list2, list3, ...):
    # работаем с e1 из list1, e2 из list2 и т.д.


Функция zip собирает из n списков (list1, list2, list3, ...) один список из n-последовательностей (кортежей). Теперь каждая переменная e1, e2, e3 и т. д. служит своеобразным курсором для своей последовательности и двигаются параллельно. Цикл останавливается как только достигается конец самого короткого списка. Разъясним применение zip на нашем примере с таблицей из двух списков Cdegrees и Fdegrees:


for C, F in zip(Cdegrees, Fdegrees):
    print  '%5d %5.1f' % (C, F)


Заметно, что этот подход более естественен, чем конструкции вида for i in range(len(Cdegrees)).

Вложение

В нашей таблице мы используем отдельный список для каждого столбца. Если нам будет нужно n столбцов, то потребуется и n списков. Однако, обычно мы представляем себе какую-то таблицу как единое целое, а не как ряд столбцов или строк. Значит, будет и более естественно использовать единый аргумент для всей таблицы. Для этого можно использовать вложенные списки:


Cdegrees = range(-20, 41, 5)  # -20, -15, ..., 35, 40
Fdegrees = [(9.0/5)*C + 32 for C in Cdegrees]

table = [Cdegrees, Fdegrees]


Стоит заметить, что индексация к определенному элементу организуется также. То есть, чтобы обратиться к первому списку, мы напишем table[0], а чтобы обратиться к третьему элементу этого списка, мы приставляем следующий индекс table[0][2]. Все это органично входит в концепцию объектов - table[0] это тоже объект и его можно индексировать как любой объект, содержащий последовательность элементов.

Если идти дальше, то с помощью zip можно организовать и более естественное заполнение таблицы:


table = []
for C, F in zip(Cdegrees, Fdegrees):
    table.append([C, F])


а с генерацией списков можно получить совсем короткое:


table = [[C, F] for C, F in zip(Cdegrees, Fdegrees)]


Срезы

В Python имеется прекрасный синтаксис для вырезания частей списков, который называется срез (sublist или slice). Срезы никогда не изменяют исходный список, создавая новый. Рассмотрим примеры. A[i:] это срез, который вырезает список из исходного списка A, начиная с индекса i до конца:

>>> A = [2, 3.5, 8, 10]
>>> A[2:]
[8, 10]


A[i:j] вырезает начиная с элемента i до j-1:

>>> A[1:3]
[3.5, 8]


A[:i] вырежет из старого списка новый список, забрав все элементы вплоть до i-1:

>>> A[:3]
[2, 3.5, 8]


A[1:-1] вырезает все элементы кроме первого и последнего (заметьте, что можно использовать отрицательную индексацию), а A[:] копирует весь список:

>>> A[1:-1]
[3.5, 8]
>>> A[:]
[2, 3.5, 8, 10]


Покажем, что срезы всего лишь копии. Доказательство 1:

>>> l1 = [1, 4, 3]
>>> l2 = l1[:-1]
>>> l2
[1, 4]
>>> l1[0] = 100
>>> l1
[100, 4, 3]
>>> l2
[1, 4]


Доказательство 2:

>>> B = A[:]
>>> C = A
>>> B == A
True
>>> B is A
False
>>> C is A
True

Равенство, которое записывается ==, как мы помним является сравнением значений элементов - если они равны, то возвращается True. Выражение is сравнивает сами объекты, если переменные ссылаются на один и тот же объект в памяти (а не на копию), то возвращается True, в обратном случае False.

Однако, возможно присваивание срезу, что может изменить длину списка и даже сделать его пустым:

>>> # Заменим некоторые элементы:
... a[0:2] = [1, 12]
>>> a
[1, 12, 123, 1234]
>>> # Удалим немного:
... a[0:2] = []
>>> a
[123, 1234]
>>> # Вставим пару:
... a[1:1] = ['bletch', 'xyzzy']
>>> a
[123, 'bletch', 'xyzzy', 1234]


Зачем же нужны срезы? Покажем на примере нашей "табличной" программы:


>>> for C, F in table[Cdegrees.index(10):Cdegrees.index(35)]:
...     print '%5.0f %5.1f' % (C, F)
...
   10 50.0
   15 59.0
   20 68.0
   25 77.0
   30 86.0


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

Больше возможностей, больше примеров

a = [] создание пустого списка
a = [1, 4.4, ’run.py’] создание списка
a.append(elem) добавить элемент elem в конец списка
a + [1,3] сложение двух списков
a.insert(i, e) вставить элемент e перед элементом с индексом i
a[3] вызов элемента с индексом 3
a[-1] вызов последнего элемента
a[1:3] срез: создание копии из элементов с индексами 1, 2
del a[3] удалить элемент с индексом 3
a.pop(3) удалить элемент с индексом 3, вернув его значение
a.remove(e) удалить элемент со значением e
a.index('run.py') индекс, соответствующий строковому элементу со значением 'run.py'
'run.py' in a узнать, содержится ли элемент в списке a
a.count(v) сосчитать сколько элементов списка a имеют значение v
len(a) число элементов в списке a
min(a) наименьший элемент в a
max(a) наибольший элемент в a
sum(a) сумма всех элементов a
sorted(a) возвращает сортированный список a
reverse(a) возвращает "перевернутый" список a
b[3][0][2] вызов элемента вложенного списка
isinstance(a, list) возвращает True, если объект a является списком

Кортежи

Кортежи (tuples) очень похожи на списки, но кортежи нельзя изменять. В отличие от списков, кортежи записываются в круглых скобках:

>>> t = (2, 4, 6, 'temp.pdf')

Но во многих случаях скобки можно отбросить:

>>> t = 2, 4, 6, 'temp.pdf'
>>> for element in 'myfile.txt', 'yourfile.txt', 'herfile.txt':
...     print element
...
myfile.txt yourfile.txt herfile.txt


Большинство возможностей из списков доступно и в кортежах:


>>> t = t + (-1.0, -2.0)
>>> t
(2, 4, 6, 'temp.pdf', -1.0, -2.0)
>>> t[1]
4
>>> t[2:]
(6, 'temp.pdf', -1.0, -2.0)
>>> 6 in t
True


Но любая попытка изменить содержание кортежа приводит к неудаче:


>>> t[1] = -1
...
TypeError: object does not support item assignment
>>> t.append(0)
...
AttributeError: tuple object has no attribute append
>>> del t[1]
...
TypeError: object doesnt support item deletion


Возникает вопрос — зачем нужны кортежи, если списки могут делать большее? Во-первых, из того, что кортеж нельзя изменить, вытекает что их можно использовать в таких случаях — когда нельзя изменять какую-то последовательность. Во-вторых, работа с кортежами происходит быстрее, чем со списками — для неизменяемых объектов требуется меньше памяти. В-третьих, кортежи часто используются в Python в качестве ключей в таком типе объектов как словари, где не могут использоваться списки.

Чему мы научились

Это очень важный урок, в котором мы узнали о двух важных областях: такой общевостребованной части программирования как циклы и об особенных объектах Python как последовательности — списки и кортежи. Без этих вещей не обходится практически ни одна программа на Python. Поэтому будет неплохо потренироваться на упражнениях.