Больше средств для управления потоком команд

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

Оператор if

Возможно, наиболее широко известный тип оператора — оператор if (если). Например:

>>> x = int(input("Введите, пожалуйста, целое число: "))
Введите, пожалуйста, целое число:  42
>>> if x < 0:
...     x = 0
...     print('Negative changed to zero')
... elif x == 0:
...     print('Zero')
... elif x == 1:
...     print('Single')
... else:
...     print('More')
...
More

Блока elif может не быть вообще, он может быть один или их может быть несколько, а блок else (иначе) необязателен. Ключевое слово `elif` — краткая запись `else if`(иначе если) — позволяет избавиться от чрезмерного количества отступов. Оператор if ... elif ... elif ... — аналог оператора выбора switch или case, которые можно встретить в других языках программирования.

Оператор for

Оператор for в Python немного отличается от того, какой вы, возможно, использовали в C или Pascal. Вместо неизменного прохождения по арифметической прогрессии из чисел (как в Pascal) или предоставления пользователю возможности указать шаг итерации и условие остановки (как в С), оператор for в Python проходит по всем элементам любой последовательности (списка или строки) в том порядке, в котором они в ней располагаются. Например (игра слов не подразумевалась):

>>> # Измерим несколько строк:
... words = ['cat', 'window', 'defenestrate']
>>> for w in words:
...     print(w, len(w))
...
cat 3
window 6
defenestrate 12

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

>>> for w in words[:]:  # Цикл по копии среза всего списка.
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

С помощью for w in words:, в примере делалась бы попытка сделать бесконечный список, вставляя defenestrate снова и снова.

Функция range()

Если вам нужно перебрать последовательность чисел, встроенная функция range() придёт на помощь. Она генерирует арифметические прогрессии:

>>> for i in range(5):
...     print(i)
...
0
1
2
3
4

Указанный конец интервала никогда не включается в сгенерированный список; вызов range(10) генерирует десять значений, которые являются подходящими индексами для элементов последовательности длины 10. Можно указать другое начало интервала и другую величину шага (даже отрицательную; иногда это называют "шагом"):

range(5, 10)
   5 through 9

range(0, 10, 3)
   0, 3, 6, 9

range(-10, -100, -30)
  -10, -40, -70

Чтобы пройти по всем индексам какой-либо последовательности, скомбинируйте вызовы range() и len() следующим образом:

>>> a = ['Mary', 'had', 'a', 'little', 'lamb']
>>> for i in range(len(a)):
...     print(i, a[i])
...
0 Mary
1 had
2 a
3 little
4 lamb

В большинстве таких случаев удобно использовать функцию enumerate(), смотрите Организация циклов.

Странные вещи начинают происходить при попытке вывода последовательности:

>>> print(range(10))
range(0, 10)

Во многих случаях объект, возвращаемый функцией range(), ведёт себя как список, но фактически им не является. Этот объект возвращает по очереди элементы желаемой последовательности, когда вы проходите по нему в цикле, но на самом деле не создаёт списка, сохраняя таким образом пространство в памяти.

Мы называем такие объекты итерируемыми (iterable), и это все объекты, которые предназначаются для функций и конструкций, ожидающих от них поочерёдного предоставления элементов до тех пор, пока источник не иссякнет. Мы видели, что оператор for является таким итератором. Функция list() тоже из их числа — она создаёт списки из итерируемых объектов:

>>> list(range(5))
[0, 1, 2, 3, 4]

Позже мы познакомимся и с другими функциями, которые возвращают и принимают итерируемые объекты в качестве параметров.

Операторы break и continue, а также условие else в циклах

Оператор break прерывает выполнение самого ближайшего вложенного цикла for или while (по аналогии с языком C).

Операторы циклов могут иметь ветвь else. Она исполняется, когда цикл выполнил перебор до конца (в случае for) или когда условие становится ложным (в случае while), но не в тех случаях, когда цикл прерывается по break. Это поведение иллюстрируется следующим примером, в котором производится поиск простых чисел:

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print(n, 'equals', x, '*', n//x)
...             break
...     else:
...         # цикл отработал безрезультатно, не найдя множитель
...         print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

(Да, этот код корректен. Посмотрите внимательно: условие else принадлежит циклу for, а не оператору if.)

При использовании в цикле условие else имеет больше общего с условием else оператора try, чем с тем же оператора if: условие else оператора try выполняется, когда не происходит исключения, а условие else цикла выполняется, когда не происходит break. Подробнее об операторе try и исключениях смотрите в Handling Exceptions.

Оператор continue, также заимствованный из C, продожает выполнение со следующей итерации цикла:

>>> for num in range(2, 10):
...     if num % 2 == 0:
...         print("Found an even number", num)
...         continue
...     print("Found a number", num)
Found an even number 2
Found a number 3
Found an even number 4
Found a number 5
Found an even number 6
Found a number 7
Found an even number 8
Found a number 9

Оператор pass

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

>>> while True:
...     pass  # Ожидание прерывания c клавиатуры (Ctrl+C) в режиме занятости
...

Этот оператор часто используется для создания минималистичных классов:

>>> class MyEmptyClass:
...     pass
...

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

>>> def initlog(*args):
...     pass   # Remember to implement this!
...

Определение функций

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

>>> def fib(n):    # вывести числа Фибоначчи меньшие (вплоть до) n
...     """Выводит ряд Фибоначчи, ограниченный n."""
...     a, b = 0, 1
...     while a < n:
...         print(a, end=' ')
...         a, b = b, a+b
...     print()
...
>>> # Теперь вызовем определенную нами функцию:
... fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

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

Первым выражением в теле функции может быть строковой литерал — этот литерал является строкой документации функции, или док-строкой (docstring). (Больше информации о док-строках вы найдёте в разделе Док-строки.) Существуют инструменты, которые используют док-строки для того, чтобы сгенерировать печатную или онлайн-документацию или чтобы позволить пользователю перемещаться по коду интерактивно; добавление строк документации в ваш код — это хорошая практика, постарайтесь к ней привыкнуть.

Исполнение функции приводит к созданию новой таблицы символов, использующейся для хранения локальных переменных функции. Если быть более точными, все присваивания переменных в функции сохраняют значение в локальной таблице символов; при обнаружении ссылки на переменную, в первую очередь просматривается локальная таблица символов, затем локальная таблица символов для окружающих функций, затем глобальная таблица символов и, наконец, таблица встроенных имён. Таким образом, глобальным переменным невозможно прямо присвоить значения внутри функций (если они конечно не упомянуты в операторе global) несмотря на то, что ссылки на них могут использоваться.

Фактические параметры при вызове функции помещаются в локальную таблицу символов вызванной функции; в результате аргументы передаются через вызов по значению (call by value) (где значение — это всегда ссылка (reference) на объект, а не значение его самого)[1]. Если одна функция вызывает другую — то для этого вызова создается новая локальная таблица символов.

При определении функции её имя также помещается в текущую таблицу символов. Тип значения, связанного с именем функции, распознается интерпретатором как функция, определённая пользователем (user-defined function). Само значение может быть присвоено другому имени, которое затем может также использоваться в качестве функции. Эта система работает в виде основного механизма переименования:

>>> fib
<function fib at 10042ed0>
>>> f = fib
>>> f(100)
0 1 1 2 3 5 8 13 21 34 55 89

Если вы использовали в работе другие языки программирования, вы можете возразить, что fib — это не функция, а процедура, поскольку не возвращает никакого значения. На самом деле, даже функции без ключевого слова return возвращают значение, хотя и более скучное. Такое значение именуется None (это встроенное имя). Вывод значения None обычно подавляется в интерактивном режиме интерпретатора, если оно оказывается единственным значением, которое нужно вывести. Вы можете проследить за этим процессом, если действительно хотите, используя функцию print():

>>> fib(0)
>>> print(fib(0))
None

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

>>> def fib2(n):  # вывести числа Фибоначчи меньшие (вплоть до) n
...     """Выводит ряд Фибоначчи, ограниченный n."""
...     result = []
...     a, b = 0, 1
...     while a < n:
...         result.append(a)    # см. ниже
...         a, b = b, a+b
...     return result
...
>>> f100 = fib2(100)    # вызвать ее
>>> f100                # записать результат
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

И на этот раз пример демонстрирует некоторые новые возможности Python:

  • Оператор return завершает выполнение функции, возвращая некоторое значение. Оператор return без аргумента возвращает None. Достижение конца функции также возвращает None.
  • Выражение result.append(b) вызывает метод объекта-списка result. Метод — это функция, которая «принадлежит» объекту и указывается через выражение вида obj.methodname, где obj — некоторый объект (может быть выражением), а methodname — имя метода, присущий объекту данного типа. Различные типы определяют различные методы. Методы разных типов могут иметь одинаковые имена, не вызывая неопределённостей. (Возможно определить ваши собственные типы и методы объектов, используя классы, см. Классы.) Метод append(), показанный в примере, определён для объектов типа список; он добавляет в конец списка новый элемент. В данном примере это действие эквивалентно выражению result = result + [a], но более эффективно.

Подробнее об определении функций

Также есть возможность определять функции с переменным количеством параметров. Для этого существует три формы, которые также можно использовать совместно.

Значения аргументов по умолчанию

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

def ask_ok(prompt, retries=4, reminder='Please try again!'):
    while True:
        ok = input(prompt)
        if ok in ('y', 'ye', 'yes'):
            return True
        if ok in ('n', 'no', 'nop', 'nope'):
            return False
        retries = retries - 1
        if retries < 0:
            raise ValueError('invalid user response')
        print(reminder)

Эта функция может быть вызвана несколькими способами:

  • задав только обязательные аргументы: ask_ok(‘Do you really want to quit?’)
  • задав один из опциональных аргументов: ask_ok(‘OK to overwrite the file?’, 2)
  • или даже задав все аргументы: ask_ok(‘OK to overwrite the file?’, 2, ‘Come on, only yes or no!’)

Этот пример также знакомит вас с ключевым словом in. Посредством его можно проверить, содержит ли последовательность определённое значение или нет.

Значения по умолчанию вычисляются в месте определения функции, в определяющей области, поэтому код

i = 5

def f(arg=i):
    print(arg)

i = 6
f()

выведет 5.

Важное предупреждение: Значение по умолчанию вычисляется лишь единожды. Это имеет значение, когда значением по умолчанию является изменяемый объект, такой как список, словарь (dictionary) или экземпляры большинства классов. Например, следующая функция накапливает переданные ей параметры:

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))
print(f(2))
print(f(3))

Это напечатает

[1]
[1, 2]
[1, 2, 3]

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

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

Именованные параметры

Функции также могут быть вызваны с использованием именованных параметров (keyword arguments) в форме kwarg=value. Например, нижеприведённая функция:

def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.")
    print("-- Lovely plumage, the", type)
    print("-- It's", state, "!")

принимает один обязательный аргумент (voltage) и три опциональных аргумента (state, actionи type). Эта функция может быть вызвана любым из следующих способов:

parrot(1000)                                          # 1 positional argument
parrot(voltage=1000)                                  # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM')             # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000)             # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump')         # 3 positional arguments
parrot('a thousand', state='pushing up the daisies')  # 1 positional, 1 keyword

но все следующие вызовы были бы неправильными:

parrot()                     # отсутствует обязательный аргумент
parrot(voltage=5.0, 'dead')  # неименованный аргумент после именованного аргумента
parrot(110, voltage=220)     # повторяющееся значение для того же аргумента
parrot(actor='John Cleese')  # неизвестный именованный аргумент

В вызове функции именованные аргументы должны следовать за позиционными аргументами. Все передаваемые именованные аргументы должны соответствовать аргументам, принимаемым функцией (например, actor не является допустимым аргументом для функции parrot), а их порядок указания неважен. Это также распространяется на не опциональные аргументы (например, parrot(voltage=1000) также валиден). Ни один аргумент не может получать значение более чем один раз. Вот пример, который не работает вследствие этого ограничения:

>>> def function(a):
...     pass
...
>>> function(0, a=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: function() got multiple values for keyword argument 'a'

Если в определении функции присутствует завершающий параметр в виде **name, он получит в качестве значения словарь (подробнее в разделе Типы-отображения — словари), содержащий все именованные параметры и их значения, исключая те, которые соответствуют формальным параметрам. Можно совместить эту особенность с поддержкой формального параметра в формате *name (описывается в следующем подразделе), который получает кортеж (tuple), содержащий все позиционные параметры, следующие за списком формальных параметров. (параметр в формате *name должен описываться перед параметром в формате **name.) Например, если мы определим такую функцию:

def cheeseshop(kind, *arguments, **keywords):
    print("-- Do you have any", kind, "?")
    print("-- I'm sorry, we're all out of", kind)
    for arg in arguments:
        print(arg)
    print("-" * 40)
    keys = sorted(keywords.keys())
    for kw in keys:
        print(kw, ":", keywords[kw])

то её можно будет вызвать так:

cheeseshop("Limburger", "It's very runny, sir.",
           "It's really very, VERY runny, sir.",
           shopkeeper="Michael Palin",
           client="John Cleese",
           sketch="Cheese Shop Sketch")

и она, конечно же, выведет:

-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch

Обратите внимание, что список имён (ключей) именованных параметровсоздается посредством сортировки содержимого списка ключей keys() словаря до печати его содержимого; если бы этого не было сделано, порядок вывода параметров был бы произволен.

Списки параметров произвольной длины

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

def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))

Обычно параметры неизвестного заранее количества (variadic) указываются последними в списке формальных параметров, поскольку включают в себя все остальные переданные в функцию параметры. Все формальные параметры, которые следуют за параметром *args, должны быть только именованными, то есть, они могут быть заданы только по имени (в отличие от позиционных параметров).

>>> def concat(*args, sep="/"):
...     return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

Распаковка списков параметров

Обратная ситуация возникает когда параметры уже содержатся в списке или в кортеже, но должны быть распакованы для вызова функции, требующей отдельных позиционных параметров. Например, встроенная функция range() ожидает отдельные параметры start и stop, соответственно. Если они не доступны раздельно, для распаковки аргументов из списка или кортежа в вызове функции используйте *-синтаксис:

>>> list(range(3, 6))            # обычный вызов с отдельными аргументами
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args))            # вызов с аргументами, распакованными из списка
[3, 4, 5]

Схожим способом, словари могут получать именованные параметры через **-синтаксис:

>>> def parrot(voltage, state='a stiff', action='voom'):
...     print("-- This parrot wouldn't", action, end=' ')
...     print("if you put", voltage, "volts through it.", end=' ')
...     print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !

Лямбда-выражения

Небольшие анонимные функции могут быть созданы с со ключевым словом lambda. Эта функция возвращает сумму от двух своих аргументов: lambda a, b: a+b. lambda-функции могут быть использованы в любом месте где требуется объект функции. При этом они синтаксически ограничены одним выражением. Семантически, они лишь «синтаксический сахар» для обычного определения функции. Как и определения вложенных функций, лямбда-функции могут ссылаться на переменные из содержащей их области видимости:

>>> def make_incrementor(n):
...     return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43

Пример выше использует лямбда-выражение для возврата функции. Другое применение — передать маленькую функцию как аргумент:

>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

Строки документации (док-строки)

Перечислим некоторые существующие соглашения по содержимому строк документации и их форматированию.

Первая строка всегда должна быть сжатой, лаконичной сводкой о назначении объекта. Для краткости, в ней не обязательно присутствие имени типа или объекта, поскольку они доступны другими способами (исключая случай, когда имя функции оказывается глаголом, описывающим суть операции). Эта строка должна начинаться с прописной буквы и оканчиваться точкой.

Если строке документации (литералу, объекту строки) требуется больше строк (физических), вторая строка должна быть пустой, визуально отделяя сводку от остального описания. Следующие строки могут быть одним или более абзацем, описывающим соглашения по вызову объекта, сторонние эффекты, и т. д.

Парсер Python не обрабатывает отступы в много-строковых литералах, поэтому инструментам, которые работают над документацией, предлагается, по желанию, делать это самим. Производится это по следующему соглашению. Первая непустая строка после первой строки литерала определяет величину отступа всего литерала документации. (Мы не можем использовать первую строку, поскольку она обычно выравнивается по открывающим кавычкам и её отступ в литерале не явен). Пробельный «эквивалент» этого отступа затем отрезается от начала всех строк литерала. Строк с меньшим отступом не должно обнаруживаться, но если они встретились, весь их начальный отступ должен быть обрезан. Эквивалентность пробельных замен может быть протестирована развертыванием табуляции (обычно, к 8 пробелам).

Вот пример многострочной док-строки:

>>> def my_function():
...     """Не делаем ничего, но документируем.
...
...     Нет, правда, эта функция ничего не делает.
...     """
...     pass
...
>>> print(my_function.__doc__)
Не делаем ничего, но документируем.

    Нет, правда, эта функция ничего не делает.

Аннотации функций

Аннотации функций — совершенно опциональная мета-информация о типах, используемых функциями, определяемых пользователем (см. PEP 484 для дальнейшей информации).

Аннотации хранятся в атрибуте __annotations__ функции как словарь и не влияют ни на какую другую часть функции. Аннотации параметров определяются двоеточием после имени параметра, за которым следует выражение, имеющее значение аннотации. Аннотация результата функции определяется литералом ->, за ним выражение, и все это между списком параметров и двоеточием, обозначающим конец оператора def. В следующем примере есть позиционный аргумент, именованный аргумент и возвращаемое значение, с аннотациями:

>>> def f(ham: str, eggs: str = 'eggs') -> str:
...     print("Annotations:", f.__annotations__)
...     print("Arguments:", ham, eggs)
...     return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'

Интермеццо: Стиль написания кода

Теперь, когда вам предстоит писать более объёмные и сложные блоки кода на Python, настало время поговорить о стиле написания кода (coding style). Код на большинстве языков программирования может быть записан (или, точнее говоря, отформатирован (formatted)) различными способами; некоторые из них более читабельны, некоторые — нет. Стремление к написанию лёгкого для прочтения другими кода всегда считалось хорошим тоном, и выбор правильного стиля для кода крайне ему способствует.

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

  • Используйте отступ в 4 пробела, не используйте табуляцию

    4 пробела легко опознаются и в случае небольших отступов (хватает места для глубоких вложений) и в случае больших отступов (приятнее читается). Табуляция вносит путаницу и лучше от неё воздержаться.

  • Разделяйте строки так, чтобы их длина не превышала 79-и символов

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

  • Используйте пустые строки для отделения функций, классов, и крупных блоков внутри функций.

  • При возможности располагайте комментарий на отдельной строке.

  • Используйте док-строки

  • Применяйте пробелы вокруг символов операций и после запятых, но не добавляйте их в конструкции со скобками: a = f(1, 2) + g(3, 4)

  • Называйте ваши классы и функции единообразно; соглашение следующее: используйте CamelCase для именования классов и нижний_регистр_с_подчёркиваниями для функций и методов Всегда используйте self в качестве имени для первого аргумента метода (обращайтесь к разделу Первый взгляд на классы за дополнительной информацией о классах и методах)

  • Не используйте в вашем коде изощрённых кодировок, если он рассчитан на использование в интернациональной среде. Умолчание Python, UTF-8, или даже простой набор ASCII всегда работает на ура в любом случае.

  • Аналогично, не используйте не ASCII-символы в идентификаторах, если есть даже малейший шанс, что люди, говорящие на другом языке, будут читать и поддерживать код.

Сноски

[1]На самом деле, вызов по ссылке на объект был бы лучшим описанием, так как, если передается мутабельный объект, то вызывающий увидит любые изменения, которые делает вызывамый с объектом (элементы, вставленные в список).