4. Управление потоком выполнения

Кроме оператора while (docs.python.org/3/reference/compound_stmts.html#while), который только что был представлен, Python знает обычные операторы управления потоком, известные из других языков, но с некоторыми особенностями.

4.1. Операторы if

Возможно наиболее хорошо известным типом оператора является if (docs.python.org/3/reference/compound_stmts.html#if). Например:

>>> x = int(input("Пожалуйста, введите число: "))
Пожалуйста, введите число: 42
>>> if x < 0:
...     x = 0
...     print('Отрицательное изменяется на 0')
... elif x == 0:
...     print('Ноль')
... elif x == 1:
...     print('Один')
... else:
...     print('Больше')
...
Больше

Частей elif (docs.python.org/3/reference/compound_stmts.html#elif) может быть больше или не быть вообще, часть else (docs.python.org/3/reference/compound_stmts.html#else) также необязательна. Ключевое слово 'elif' есть сокращение от 'else if' и полезно для избежания излишних отступов. Последовательность if ... elif ... elif ... есть замена для операторов switch или case, встречающихся в других языках.

4.2. Операторы for

В Python оператор for (docs.python.org/3/reference/compound_stmts.html#for) немного отличается от того, что вы могли использовать в C или Pascal. Вместо того, чтобы всегда выполнять итерацию по арифметической прогрессии чисел (как в Pascal), или давая пользователю возможность определить как шаг итерации, так и условие остановки (как в C), в Python оператор for перебирает элементы любой последовательности (список или строку) в том порядке, в котором они появляются в последовательности. Например (нет намеренной игры слов):

>>> # Измерить несколько слов:
... 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 опять и опять.

4.3. Функция range()

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

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

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

range(5, 10)
   от 5 по 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 именно такой iterator (итератор - прим. пер.). Функций list() есть другой; она создает списки из итерируемых (перечисляемых) объектов:

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

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

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

Оператор break (docs.python.org/3/reference/simple_stmts.html#break), как и в C, прерывает выполнение вмещающего (самого внутреннего) его цикла for (docs.python.org/3/reference/compound_stmts.html#for) или while (docs.python.org/3/reference/compound_stmts.html#while).

Операторы цикла могут иметь условие 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 (docs.python.org/3/reference/compound_stmts.html#try), чем if (docs.python.org/3/reference/compound_stmts.html#if): ветка else оператора try срабатывает, когда исключения не происходит, а ветка else цикла срабатывает, когда не срабатывает break. За дополнительной информацией по оператору try и исключениям, см. Обработка исключений.

Оператор continue (docs.python.org/3/reference/simple_stmts.html#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

4.5. Оператор pass

Оператор pass (docs.python.org/3/reference/simple_stmts.html#pass) ничего не делает. Он может быть использован, когда синтаксически требуется какой-нибудь оператор, но программа не требует действия. Например:

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

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

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

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

>>> def initlog(*args):
...     pass   # Вспомните потом реализовать это!
...

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

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

>>> 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 (docs.python.org/3/reference/compound_stmts.html#def) вводит определение функции. За ним должно следовать имя функции и в круглых скобках список формальных параметров. Выражения, которые формируют тело функции, начинаются на следующей строке и должны быть с отступом.

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

Выполнение функции вводит новую таблицу обозначений, используемую для локальных переменных функции. Точнее, все присваивания переменным в функции сохраняют значение в локальной таблице обозначений; так ссылки на переменные сначала ищутся в локальной таблице, затем в локальной таблице обрамляющих функций, затем в глобальной таблице обозначений, и наконец в таблице встроенных имен. Поэтому глобальные переменные не могут быть связаны со значением непосредственно в функции (если не названы в операторе global (docs.python.org/3/reference/simple_stmts.html#global)), хотя на них можно ссылаться.

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

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

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

Придя из других языков, вы можете подумать, что объект fib не функция, а процедура, поскольку она не возвращает значение. В действительности даже функции без оператора return (docs.python.org/3/reference/simple_stmts.html#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 (docs.python.org/3/reference/simple_stmts.html#return) возвращает значение из функции. return без выражения аргумента возвращает None. Выполнение функции до конца также возвращает None.
  • Выражение result.append(a) вызывает метод спискового объекта result. Метод - это функция, которая "принадлежит" объекту и называется obj.methodname, где obj - это какой-либо объект (это может быть выражение), а methodname - имя метода, который определен типом объекта. Различные типы определяют разные методы. Методы разных типов могут иметь одинаковые имена, при этом не возникает неоднозначности. (Возможно определить ваши собственные типы объектов и методы, используя классы, см. Классы.) Метод append(), показанный в примере, определен для списковых объектов; он добавляет новый элемент в конец списка. В данном примере это эквивалентно result = result + [a], но более эффективно.

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

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

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

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

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 (docs.python.org/3/reference/expressions.html#in). Оно проверяет содержит ли последовательность определенное значение.

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

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

напечатает 5.

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

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

4.7.2. Аргументы с ключевым словом

Функции также могут быть вызваны с использованием keyword arguments (docs.python.org/3/glossary.html#term-keyword-argument) вида 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 позиционный аргумент
parrot(voltage=1000)                                  # 1 аргумент ключевое слово
parrot(voltage=1000000, action='VOOOOOM')             # 2 аргумента ключевых слова
parrot(action='VOOOOOM', voltage=1000000)             # 2 аргумента ключевых слова
parrot('a million', 'bereft of life', 'jump')         # 3 позиционных аргумента
parrot('a thousand', state='pushing up the daisies')  # 1 позиционный, 1 ключевое слово

но все следующие вызовы будут считаться недействительными:

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, функция принимает словарь (см. Типы отображений — dict), включающий все аргументы с ключевыми словами за исключением тех, которые уже соответствуют формальным параметрам. Это может быть скомбинировано с формальным параметром в форме *name (описано в следующем подразделе), который получает кортеж, включающий позиционные аргументы за списком формальных параметров. (*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

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

4.7.3. Произвольные списки аргументов

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

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'

4.7.4. Распаковка списков аргументов

Происходит обратная ситуация, когда аргументы уже в списке или кортеже, но должны быть распакованы при вызове функции, требующей отдельных позиционных аргументов. Например, встроенная функция 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 !

4.7.5. Lambda-выражения

С помощью ключевого слова lambda (docs.python.org/3/reference/expressions.html#lambda) может быть создана маленькая анонимная функция. Эта функция возвращает сумму ее двух аргументов: lambda a, b: a+b. Lambda-функции могут быть использованы там, где требуются объекты-функции. Синтаксически они ограничены одним выражением. Семантически они являются просто синтаксическим сахаром для обычного определения функции. Как вложенные определения функции lambda-функции могут ссылаться на переменные из содержащей области:

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

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

>>> 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')]

4.7.6. Строки документации

Здесь несколько соглашений о содержании и форматировании строк документации.

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

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

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

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

>>> def my_function():
...     """Do nothing, but document it.
...
...     No, really, it doesn't do anything.
...     """
...     pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
 
    No, really, it doesn't do anything.

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

Function annotations (docs.python.org/3/reference/compound_stmts.html#function) являются полностью необязательными метаданными о типах, используемых пользовательскими функциями (см. PEP 484 (python.org/dev/peps/pep-0484) для получения дополнительной информации).

Аннотации хранятся в атрибуте __annotations__ функции как словарь и не имеют никакого эффекта на любую другую часть функции. Параметры аннотаций определяются двоеточием после имени параметра, за которым следует выражение, оценивающее значение аннотации. Возвращения аннотации определяются литералом ->, следующим выражению, между списком параметра и двоеточием, обозначая конец оператора def (docs.python.org/3/reference/compound_stmts.html#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'

4.8. Стиль кодирования

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

Для Python, появился PEP 8 (python.org/dev/peps/pep-0008) как руководство по стилю, которого придерживается большинство проектов; он поощряет хорошо читаемый и приятный для глаз стиль кодирования. Каждый разработчик Python должен прочитать это однажды; вот наиболее важные извлеченные пункты для вас:

  • Используйте 4-х-пробельный отступ, а не табуляцию.
    4 пробела - хороший компромисс между маленьким отступом (позволяет большую глубину вложенности) и большим отступом (легче для чтения). Табуляции вносят путаницу, и лучше опустить их.
  • Делайте строки длиной не более 79 символов.
    Это поможет пользователям с маленькими дисплеями и сделает возможным расположить рядом несколько файлов с кодом на больших дисплеях.
  • Используйте пустые строки для отделения функций и классов, а большие блоки кода помещайте внутрь функций.
  • Когда возможно, оставляйте комментарии на их собственной строке.
  • Используйте строки документации.
  • Используйте пробелы вокруг операторов и после запятой, но не непосредственно внутри конструкции скобок: a = f(1, 2) + g(3, 4).
  • Называйте ваши классы и функции последовательно; соглашением является использование CamelCase ("верблюжьей" нотации - прим. пер.) для классов и lower_case_with_underscores (нижнего регистра с подчеркиванием - прим. пер.) для функций и методов. Всегда используйте self в качестве имени первого аргумента метода (см. Первый взгляд на классы для большей информации о классах и методах).
  • Не используйте причудливые кодировки, если ваш код предназначен для использования в международной среде. По умолчанию в Python UTF-8 или даже обычный ASCII работает лучше в любом случае.
  • Точно так же не используйте не-ASCII символы в идентификаторах, если есть хотя бы малейший шанс, что люди, говорящие на другом языке, будут читать или поддерживать этот код.

Примечания

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

Создано

Обновлено