4. Дополнительные средства управления потоком

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

4.1. Оператор if

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

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

Может быть ноль или более elif блоков, а else блок опционален. Ключевое слово „elif“ является короткой записью „else if“ и полезно во избежание чрезмерного углубления. Оператор ifelifelif… является заменой оператора switch или case, которые можно встретить в других языках программирования.

4.2. Оператор 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 user, status in users.copy().items():
    if status == 'inactive':
        del users[user]

# Стратегия: создать новую коллекцию
active_users = {}
for user, status in users.items():
    if status == 'active':
        active_users[user] = status

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

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

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

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

range(5, 10)
   5, 6, 7, 8, 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(), см. в Методах перебора элементов.

Если просто напечатать range, то происходит что-то странное:

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

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

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

>>> sum(range(4))  # 0 + 1 + 2 + 3
6

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

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

В главе Структуры данных мы рассмотрим более подробно list().

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

Как и в случае с C, оператор break выходит из самой внутренней обёртки цикла for или while.

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

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print(n, ' равно ', x, '*', n//x)
...             break
...     else:
...         # цикл закончился, не найдя множителя
...         print(n, ' является простым числом')
...
2 является простым числом
3 является простым числом
4 равно 2 * 2
5 является простым числом
6 равно 2 * 3
7 является простым числом
8 равно 2 * 4
9 равно 3 * 3

Да, это правильный код. Внимательно посмотрите на замечание else принадлежащее циклу for, а не if оператору.

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

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

>>> for num in range(2, 10):
...     if num % 2 == 0:
...         print("Нашёл чётное число ", num)
...         continue
...     print("Нашёл нечётное число ", num)
Нашёл чётное число 2
Нашёл нечётное число 3
Нашёл чётное число 4
Нашёл нечётное число 5
Нашёл чётное число 6
Нашёл нечётное число 7
Нашёл чётное число 8
Нашёл нечётное число 9

4.5. Оператор pass

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

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

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

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

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

>>> def initlog(*args):
...     pass   # Не забудьте реализовать это!
...

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

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

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

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

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

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

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

>>> 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."""
...     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(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('Вы действительно хотите выйти?')
  • Передав только один опциональный аргумент ask_ok('Можно перезаписать файл?', 2)
  • Или передав все аргументы: ask_ok('Вы точно хотите перезаписать файл?', 2, 'Хватит, либо yes, либо no!')

В этом примере также вводится ключевое слово 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. Ключевые аргументы

Функции также можно вызывать с помощью ключевых аргументов формы 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 <module>
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)
    for kw in keywords:
        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.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch

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

4.7.3. Специальные параметры

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

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

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |     Позиционные или ключевые   |
        |                                - Только ключевые
         -- Только позиционные

Где / и * являются необязательными. При использовании этих символов определяют способ передачи аргументов в функцию: позиционно, позиционно-ключевым, и только ключевым. Ключевые аргументы также называют именованными параметрами.

4.7.3.1. Позиционные или ключевые аргументы

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

4.7.3.2. Только позиционные параметры

Рассматривая более подробно определённые параметры, можно отметить их как только позиционные. При только позиционной передаче параметров имеет значение порядок, и параметры не могут передаваться по ключевому аргументу. Только позиционные параметры находятся перед / (косая черта). Логически / используется только для отделения позиционных параметров от остальных параметров. При отсутствии / в определении функции, она не содержит только позиционных параметров.

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

4.7.3.3. Только ключевые аргументы

Чтобы пометить параметры как только ключевые, необходимо передать их по ключевому аргументу поместив * в список аргументов непосредственно перед первым только ключевым параметром.

4.7.3.4. Примеры функции

Рассмотрим следующие примеры определений функций с акцентированием внимания на маркеры / и *:

>>> def standard_arg(arg):
...     print(arg)
...
>>> def pos_only_arg(arg, /):
...     print(arg)
...
>>> def kwd_only_arg(*, arg):
...     print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
...     print(pos_only, standard, kwd_only)

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

>>> standard_arg(2)
2

>>> standard_arg(arg=2)
2

Вторая функция pos_only_arg ограничена только позиционными аргументами при наличии / в определении функции:

>>> pos_only_arg(1)
1

>>> pos_only_arg(arg=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'
Третья функция kwd_only_args разрешает только ключевые аргументы, когда указана

* в определении функции:

>>> kwd_only_arg(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given

>>> kwd_only_arg(arg=3)
3

И последняя использует все три соглашения в одном определении функции:

>>> combined_example(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given

>>> combined_example(1, 2, kwd_only=3)
1 2 3

>>> combined_example(1, standard=2, kwd_only=3)
1 2 3

>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: combined_example() got an unexpected keyword argument 'pos_only'

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

def foo(name, **kwds):
    return 'name' in kwds

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

>>> foo(1, **{'name': 2})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>

Но использование / (только позиционные аргументы) делает возможным использовать name как позиционный аргумент и 'name' как ключ в ключевых аргументах:

def foo(name, /, **kwds):
    return 'name' in kwds
>>> foo(1, **{'name': 2})
True

Другими словами, имена только позиционных параметров могут использоваться в **kwds без двусмысленности.

4.7.3.5. Резюме

Вариант использования определит, какие параметры использовать в определении функции:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

Советы:

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

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

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

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

Как правило, вариативные аргументы являются последними в списке формальных параметров, потому что они собирают все оставшиеся входные аргументы, передаваемые в функцию. Любые формальные параметры, находящиеся после *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.5. Распаковка списка аргументов

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

>>> 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.6. Лямбда-выражения

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

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

4.7.7. Докстринги

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

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

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

Далее пример многострочного докстринга:

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

    Нет, правда, ничего не делает.

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

Аннотация функций совершенно необязательная мета-информация о типах, используемых функциями, определяемых пользователем (см. PEP 3107 и 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'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Сноски

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