Введение

Этот документ — краткая справка. За подробностями лучше обращаться к официальной документации.

Общая структура программы

Программа на языке Python представляет собой последовательность команд (statements): каждая команда — на отдельной строчке (допускается также разделять некоторые команды точкой-с-запятой).

Самые частовстречающиеся команды:

  • вычислить значение формулы (оформляется этой самой формулой)
  • изменить что-нибудь (оформляется знаком =, справа от которого стоит формула, а слева — то, что нужно изменить)
  • ветвление (оформляется при помощи слов if, elif, else)
  • цикл (оформляется при помощи слов for или while)
  • определение подпрограмм (оформляется при помощи слова def)
  • использование внешнего модуля (оформляется при помощи слова import)

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

Синтаксис формул

Формулы (expressions) — отдельная грамматическая категория. Команды внутри формул не встречаются, а вот формулы являются частью почти всех команд!

Формулы составляются из литералов, переменных и операторов.

Переменные

С точки зрения грамматики переменная (variable/identifier) — одно слово, не совпадающее ни с одним из небольшого набора зарезервированных (таких как if, else и т.п.). В слове допускаются буквы, нижние подчёркивания, цифры (но цифра не может стоять в начале слова).

Значением (value/binding) переменной является объект, к которому она привязана в данный момент. Изменить привязку можно командой привязки:

переменная = формула

которая сначала вычисляет значение формулы, а потом привязывает указанную переменную к этому значению.

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

переменная1, переменная2 = формула1, формула2

Операторы

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

Среди операторов наиболее часто встречаются:

  • вызов функции XXX(YYY)
  • доступ к атрибуту XXX.YYY
  • индексация коллекции XXX[YYY]
  • проверка на вхождение XXX in YYY
  • сумма XXX + YYY, разность XXX - YYY, произведение XXX * YYY
  • частное XXX / YYY, неполное частное XXX // YYY, остаток XXX % YYY
  • унарный минус -XXX
  • степень XXX ** YYY
  • эквивалентность XXX == YYY, неэквивалентность XXX != YYY
  • строгие неравенства XXX < YYY и XXX > YYY
  • нестрогие неравенства XXX <= YYY и XXX >= YYY
  • логические операции: не not XXX, и XXX and YYY, или XXX or YYY
  • поразрядные/поэлементные операции: и XXX & YYY, или XXX | YYY, сумма XXX ^ YYY

Литералы

Литералами называются специальные обозначения для некоторых объектов. Можно считать их операторами с уникальным (для каждого из них) синтаксисом и правилом вычисления.

Стандарт языка Python к литералам относит обозначения для текстовых и числовых литералов. По факту обозначения для некоторых коллекций и функций тоже зачастую относят к литералам (стандарт языка относит их к операторам).

Наиболее часто встречаются:

  • «тривиальное» значение None
  • булевы True и False
  • числа 123, 12.3, 1.23e1, 2+3j и тому подобное
  • тексты 'foo' или "foo" (одинарные и двойные кавычки не различаются)
  • функции lambda x,y: x + 2*y
  • списки (массивы) [1,2,3]
  • кортежи (неизменяемые массивы) (1,2,3)
  • множества {1,2,3}
  • словари (ассоциативные массивы) {"foo": 1, 3: "bar", 5+1: 3+1}

Семантика формул

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

Семантика формул

Ещё раз напомним, что формулы в Python (впрочем, как и в большинстве других языков) составляются из переменных, операторов и литералов. А именно, формула — это одно из трёх:

  • переменная
  • литерал
  • оператор и последовательность (возможно, пустая) формул, называемых подформулами или операндами этого оператора

У каждой формулы есть связанная с ней процедура вычисления её значения:

  • значением переменной является её текущая привязка
  • значение литерала вычисляется по связанному с этим литералом правилу
  • значение оператора (за редким исключением) вычисляется так: сперва слева направо вычисляются значения всех подформул, затем к этим значениям применяется правило, связанное с этим оператором

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

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

Например, формула print("Hello world!") имеет побочный эффект (отправка текста Hello world на стандартный выход программы), а формула 2+3*4 побочных эффектов не имеет (если таковым эффектом не считать нагрев окружающей среды в результате работы ЭВМ).

Арифметика и логика

Арифметика

  • сумма XXX + YYY, разность XXX - YYY, произведение XXX * YYY
  • частное XXX / YYY, неполное частное XXX // YYY, остаток XXX % YYY
  • унарный минус -XXX
  • степень XXX ** YYY
  • эквивалентность XXX == YYY, неэквивалентность XXX != YYY
  • строгие неравенства XXX < YYY и XXX > YYY
  • нестрогие неравенства XXX <= YYY и XXX >= YYY

Всё более-менее стандартно по модулю одной особенности: неполное частное — результат округления соответствующей дроби вниз, а не к нулю (как принято в большинстве языков программирования). При этом всегда выполняется

a == (a // b) * b + (a % b)

Следует быть аккуратным со следующими операторами:

  • с делением (/): его результат — число с плавающей точкой; арифметика с плавающей точкой на практике не пригодна ни для каких целей, кроме физического моделирования; любое другое использование арифметики с плавающей точкой требует высокой аккуратности и глубокого понимания её устройства
  • с остатком (%): у него есть оператор-омоним, форматирующий текст; применяется этот самый форматирующий оператор, если левый операнд имеет текстовый тип

Логика

В качестве основных логических значений используются «истина» True и «ложь» False. К ним можно применять конъюнкцию (& или and), дизъюнкцию (| или or), сложение ^ и отрицание not.

Отдельно выделим and и or: они вычисляют свой правый операнд только если левый истинный (для and) и ложный (для or). Ложными считаются None, False, всевозможные нули и всевозможные пустые контейнеры.

Остальные объекты встроенных типов считаются истинными.

Также можно встретить двуинфиксный тернарный оператор ... if ... else ...:

  • первым делом он вычисляет свой второй операнд
  • если он истинный, то вычисляется первый операнд
  • в противном случае вычислятеся второй операнд

В частности:

  • A and B эквивалентно B if A else A
  • not A эквивалентно False if A else True
  • A or B эквивалентно B if not A else A

Поскольку это — единственный чисто тернарный оператор в языке, его так и называют «тернарным оператором».

Поразрядные операции

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

Основные поразрядные операции в языке следующие:

  • :& — поразрядная конъюнкция (результат состоит из тех и только тех двоичных разрядов, которые есть в обоих входных числах)
  • :| — поразрядная дизъюнкция (результат состоит из тех и только тех двоичных разрядов, которые есть хотя бы в одном из входных чисел)
  • :^ — поразрядный XOR (результат состоит из тех и только тех двоичных разрядов, которые есть ровно в одном из двух входных чисел)
  • :<< — сдвиг влево (с дописыванием нулей в младшие разряды)
  • :>> — сдвиг вправо (с уничтожением младших разрядов)

Рекомендуется их применять лишь к целым неотрицательным числам.

Структуры данных

Списки/массивы

Список работает как набор «безымянных переменных», называемых его элементами. Элементы нумеруются последовательными натуральными числами (начиная с нуля).

Элемент списка можно получить при помощи формулы список[номер], где список — формула, значением которой является интересующий нас список, а номер — формула, значением которой является интересующий нас номер элемента.

Узнать количество элементов списка можно функцией len.

Изменить элемент списка можно командой изменения. Например:

foo[3] = 5  ## привязать третий элемент к пятёрке

Очень важный момент: каждый списковый литерал создаёт новый список, а перепривязка переменной — нет.

foo = [1,2,3,4,5]
bar = foo         # bar привязана к тому же списку, что и foo
bar[0] = 5        # теперь и foo[0] == 5
baz = [1,2,3,4,5] # а это -- уже совсем другой список
bar[1] = baz[1]   # первый элемент списка bar теперь привязан к 
                  # значению формулы baz[1], то есть числу 2 
                  # (но не к первому элементу списка baz!)

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

foo = [1, [2, 3], 4]
bar = foo[1:3] # копируем элементы списка с 1-го (включая) по 3-й (исключая)
bar[1] = 5     # foo[2] по-прежнему 4
bar[0][0] = 5  # теперь и foo[1][0] == 5: срез делает "неглубокую" копию

В срезе можно опустить один или оба индекса. Тогда они считаются равными нулю и количеству элементов списка соответственно. В частности, foo[:] делает копию всего списка foo.

Также допускаются срезы с тремя индексами (подробности — в документации).

Добавить новые элементы к списку можно при помощи метода (атрибута-подпрограммы) append: foo.append(123) добавляет в конец списка foo элемент, привязанный к числу 123.

Убрать элемент с конца списка можно методом pop вот так: foo.pop(). Хотя pop можно применять и к конкретному номеру, удаление элементов из списка занимает время, пропорциональное разности длины списка и номера удаляемого элемента.

List comprehesion

Также часто можно встретить т.н. «теоретико-множественную» запись:

[x ** 2 for xs in ys for x in xs if x < 3]

В общем случае такая запись выглядит как расположенная внутри квадратных скобок формула, за которой следует цепочка for ... in ... или if ....

Вычисление такой формулы происходит ровно так же, как и вычисление переменной result в следующем коде:

result = []

for xs in ys:
    for x in xs:
        if x < 3:
            result.append(x ** 2)

То есть соответствующие циклы и ветвления распологаются в той же последовательности друг внутри друга, а формула становится второй подформулой оператора вызова функции (первой подформулой которого становится result.append).

Естественно, никакой переменной result при вычислении «теоретико-множественных записей» не происходит.

Кортежи

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

foo = 1, 2, 3
bar = (1, 2, 3)

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

Несмотря на неизменяемость кортежей, никто не мешает на основе список и кортежей делать, например, «списки постоянной длины»:

foo = ([1], [2], [3])
foo[1][0] = 5  # теперь foo == ([1], [5], [3])

# можно даже спрятать соответствующие формулы с [0] за функциями
def get_elem(a, i): return a[i][0]
def set_elem(a, i, v): a[i][0] = v
# теперь, не зная о внутренней структуре "списков постоянной длины", их
# "элементы" невозможно (зло)намеренно удлинить

Тексты

Ведут себя как кортежи одноэлементых текстов (как бы странно подобная рекуррентная семантика ни выглядела). Например, "f", "foo"[0], "foo"[0][0], "foo"[0][0][0] и так далее — одно и то же.

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

text = ""
for x in range(N):
    text += str(x)

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

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

То есть вместо str(3) + " + " + str(5) + " = " + str(3+5) рекомендуется писать f"{3} + {5} = {3+5}".

Байтовые кортежи

Функция bytes позволяет преобразовать последовательность чисел, находящихся в пределах от 0 до 255, в байтовый кортеж. Байтовые кортежи требуются некоторыми функциями ввода-вывода.

Отметим, что в Python встроен кодировщик/декодировщик текстовой кодировки UTF-8:

  • bytes(текст, "utf-8") — байты кодировки текста
  • str(байты, "utf-8") — текст, закодированный байтами

Словари

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

{}  ## пустой словарь
{ "foo": 3, 3: "bar", 4: None }  ## ключ "foo" привязан к 3
                                 ## ключ 3 привязан к "bar"
                                 ## ключ 4 привязан к None

foo = {}
foo["foo"] = 3
foo[3] = "bar"
foo[4] = None  ## теперь переменная foo привязана к словарю, 
               ## эквивалентному таковому из второй строчки

del foo[3]  ## привязки можно удалять; это -- быстрая операция
3 in foo    ## наличие привязки можно проверить; это -- быстрая операция
len(foo)    ## количество привязок можно посчитать; тоже быстрая операция

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

Также нужно учитывать, что for проходится по словарю в произвольном порядке (конкретный произвол которого в районе версии языка 3.6 сильно менялся).

Множества

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

set()          ## пустое множество
{1, 2, "foo"}  ## трёхэлементное множество

foo = set()
foo.add(1)
foo.add(2)
foo.add("foo")  ## теперь переменная foo привязана к множеству, 
                ## эквивалентному таковому из второй строчки

Функциональные выражения

Перед изучением этого раздела настоятельно рекомендуется прочитать про команду привязки/перепривязки.

Теорминимум

Питон поддерживает так называемые лямбда-выражения с синтаксисом

lambda переменные_через_запятую: формула

Лямбда-выражения имеют нетривиальную семантику взаимодействия с оператором применения. А именно, если значение формулы FOO является значением лямбда-выражения lambda BAR: BAZ, то формула

FOO(QUUZ)

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

  • вычисляется FOO
  • вычисляются все формулы последовательности QUUZ
  • для каждой из переменных BAR создаётся временная привязка к значению соответствующей формулы QUUZ
  • в контексте этих привязок вычисляется формула BAZ
  • значение которой становится значением всей формулы FOO(QUUZ)

Рассмотрим, например, программу

x = 1
print((lambda x: x+2)(x+1))
print(x)

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

  • создаётся привязка x к числу 1
  • вычисляется значение переменной print: это — встроенная подпрограмма вывода
  • вычисляется значение формулы lambda x: x+2: можно считать, что её значение — она сама
  • вычисляется значение переменной x: это — её текущая привязка, то есть число 1
  • вычисляется значение литерала 1: это — число 1
  • вычисляется значение оператора сложения, на вход которому поданы две единицы: это — число 2
  • создаётся временная привязка x к числу 2
  • в контексте этой привязки вычисляется x: это — число 2
  • вычисляется значение литерала 2: это — число 2
  • вычисляется значение оператора сложения, на вход которому поданы две двойки: это — число 4
  • временная привязка x к числу 2 удаляется
  • оператору применения подаются на вход встроенная подпрограмма вывода и число 4: его результат — специальное значение None
  • как побочный эффект вычисления значения оператора применения программа отправляет 4 на стандартный вывод
  • вычисляется значение переменной print: это — встроенная подпрограмма вывода
  • вычисляется значение переменной x: сейчас это — число 1
  • оператору применения подаются на вход встроенная подпрограмма вывода и число 1: его результат — специальное значение None
  • как побочный эффект вычисления значения оператора применения программа отправляет 1 на стандартный вывод

Итого мы увидим на стандартном выводе программы числа 4 и 1.

Лексические замыкания

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

  • контекст (набор привязок), в котором вычисляется подформула лямбда-выражения — это тот контекст, в котором было вычислено само лямбда-выражение

Необходимый пример-пояснение:

x = 2
foo = (lambda x: (lambda: print(x)))(1)
print(x) ## печатает 2
foo()    ## печатает 1

В этой программе foo() отправляет на стандартный вывод число 1, так как лямбда-выражение lambda: print(x) было вычислено в контексте временной привязки x --> 1, которая в тот момент закрывала глобальную привязку x --> 2.

К сожалению, лексические замыкания имеют неоднозначные взаимоотношения с перепривязкой переменных. А именно, на первый взгляд программа

x = 1
foo = lambda: print(x)
x = 2
foo()

должна была бы печатать 1, так как лямбда-выражение было вычислено в контексте привязки x --> 1.

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

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

x = 1
foo = fn -> IO.puts(x) end
x = 2
foo.()

печатает именно 1, а не 2, поскольку в Elixir команда привязки всегда создаёт новую привязку.

Замыкания и рекурсия

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

  • если переменной в контексте нет, она там создаётся
  • затем вычисляется формула в правой части
  • затем привязка переменной меняется на значение формулы

Это позволяет успешно работать следующей программе:

fac = lambda n: (1 if n < 2 else n * fac(n-1))
print(fac(5))

Семантика основных команд

Напомним, что программа представляет собой последовательность команд.

К ним, в частности, относятся:

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

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

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

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

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

Привязка и перепривязка

Команда привязки переменной оформляется так:

переменная = формула

После выполнения этой команды значение формулы получает новое имя — переменную, упомянутую слева от знака равенства.

Если нужно это имя дать другому объекту, нужно ещё раз воспользоваться командой привязки с той же переменной.

Привязку можно вообще стереть командой

del переменная

Также поддерживается деструктурирующая привязка:

шаблон = формула

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

Например, работает следующее:

[x, y], z = (1, 2), 3

В шаблоне можно использовать звёздочку перед последним элементом кортежа. Тогда он окажется привязан ко всему соответствующему участку последовательности (преобразованному в список):

x, *xs = 1, 2, 3, 4
print(x)  ## 1
print(xs) ## [2, 3, 4]

Условия и циклы

Предисловие

Этот раздел посвящён командам if и while.

Дадим здесь их краткий обзов.

Ветвление

Команда if имеет следующий синтаксис:

if формула:
  команды
elif формула:
  команды
elif формула:
  команды
...
elif формула:
  команды
else:
  команды

Все elif (которых может быть сколько угодно) и else (которого не может быть больше одного) являются опциональными.

Выполняется такая команда следующим образом:

  • вычисляется первая формула, если её значение «истинно» (что именно означает «истинность», пока уточнять не будем), выполняется первая последовательность команд; на этом исполнение if завершается
  • в противном случае вычисляется вторая формула, если её значение «истинно» выполняется вторая последовательность команд; на этом исполнение if завершается
  • в противном случае вычисляется первая формула, если её значение «истинно», выполняется третья последовательность команд; на этом исполнение if завершается
  • и так далее
  • если никакая из формул не дала «истинного» значения, выполняются команды, которые записаны в блоке else:

Важно! все команды внутри одной последовательности должны начинаться с одной и той же колонки, причём более правой, чем та, в которой стоят слова if, elif, else. В случае, если команда одна, допускается её ставить в ту же строчку, что и соответствующий ей if, elif, else.

Цикл

Команда while имеет следующий синтаксис:

while формула:
  команды

(есть ещё более сложный while .. else, но мы не будем его трогать, тем более что почти нигде, кроме Питона, он не встречается).

Вышеупомянутая команда while выполняется так, как выполнялся бы бесконечный код

if формула:
  команды
  if формула:
    команды
    if формула:
      команды
      ...

Из цикла можно преждевременно выйти командами continue и break:

  • первая из них пропускает все оставшиеся до следующей проверки истинности формулы команды
  • вторая — завершает выполнение всей команды while

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

i = 1
while print("проверка условия в", i, "раз") == None:
  print("перед break")
  if i > 10: break
  print("перед continue")
  i = i + 1
  continue
  print("после continue")
  i = i + 2

Истинность

Теперь чуть-чуть поговорим о вышеупомянутой «истинности»:

  • специальная константа True истинна
  • специальная константа False — нет
  • другие объекты тоже имеют свои истинности, но как именно эти истинности вычисляются — уточнять не будем: это не настолько важно
  • операторы ==, <, <=, >, >= ведут себя так, как и ожидается от «равно», «меньше», «меньше или равно», «больше», «больше или равно»
  • операторы and, or, not реализуют логические конъюнкцию, дизъюнкцию и отрицание
  • первые два из них имеют специальные правила вычисления формул, для которых они являются головой: а именно, они вычисляют свою правую подформулу только если первая оказалась истинной (для and) или ложной (для or)

Функции

Рекомендуем предварительно ознакомиться с разделом про лямбда-выражения.

Функции в Питоне — по сути многокомандные лямбда-выражения:

  • лямбда-выражение содержит одну формулу
  • функция содержит последовательность команд

Поскольку в рамках используемой Питоном модели программа — это последовательность команд, то функции в рамках такой модели (и сходных с ней) часто называют подпрограммами.

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

Синтаксис

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

def ПЕРЕМЕННАЯ(ПЕРЕМЕННАЯ, ПЕРЕМЕННАЯ, ...):
  КОМАНДА
  КОМАНДА
  ...
  КОМАНДА

Последовательность переменных внутри скобок может быть пустой. Последовательность команд пустой быть не может (если хочется аналог пустой последовательности — можно воспользоваться командой :pass).

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

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

Выход из функции

Для завершения работы функции используется команда

return ФОРМУЛА

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

Если формулу не написать, то считается, что формула None.

Если вообще не написать нигде return в определении функции, то считается, что последней командой функции является return None.

Привязки внутри функции

Привязывающие команды (= и def) внутри функции работают несколько необычно: если в определении функции хоть где-то встречается такая команда, то, независимо от наличия привязки упомянутой в этой команде переменной, такая команда создаёт новую привязку, причём временную — на время работы функции.

Примеры:

x = 1
def foo():
  print(x)

foo()  ## 1

def foo():
  x = 2
  print(x)

foo()    ## 2
print(x) ## 1

def foo():
  print(x)
  x = 2

foo()  ## ОШИБКА ВЫПОЛНЕНИЯ

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

А именно, такая переменная вычисляется при помощи локальной привязки, а если локальная привязка ещё не создана — вычисление завершается ошибкой.

Исключения

Скоро будет

Идиомы

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

Об особенностях языка Python

Основное назначение языка Python: написание сценариев (или скриптов, как их ещё называют) — коротких программ, связывающих другие более сложные программы в единое целое. Одним из следствий этого оказался непродуманный и очень неудачный дизайн языка, сильно затрудняющий программирование чего-либо, отличного от простых сценариев (и, к сожалению, со временем питон становится всё хуже и хуже в плане дизайна).

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

Структура программы

В начале программы идут импорты разнообразных модулей.

Не рекомендуется захламлять пространство имён импортами вида

from foobar import *

или даже

from foobar import foo, bar

Если не нравится слишком длинное имя модуля, то гораздо лучше этот модуль переименовать, чем импортировать его функции без префикса:

import sqlite3 as db

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

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

Функции

Разбиение программы на функции с чётко очерченными задачами и регламентом использования позволяет обратить длинную последовательность команд (которой, собственно, программа на Python и является) в гораздо более легко воспринимаемую форму. Сравните, например, такую реализацию сортировки «пузырьком»:

n = int(input())

to_sort = []

i = 0
while i < n:
    to_sort.append(int(input()))
    i += 1

i = 0
while i < n:
    j = 0
    while j < n-1:
        if to_sort[j+1] < to_sort[j]:
            to_sort[j], to_sort[j+1] = to_sort[j+1], to_sort[j]
        j += 1
    i += 1

i = 0
while i < n:
    print(to_sort[i], end=' ')
    i += 1

print()

с такой:

def main():
    to_sort = read_array()
    bubble_sort(to_sort)
    print_array(to_sort)
    
def read_array():
    n = read_int()
    return [read_int() for i in range(n)]

def read_int():
    return int(input())

def bubble_sort(array):
    n = len(array)
    for i in range(n): bubble_step(array)
    
def bubble_step(array):
    n = len(array)
    for j in range(n-1):
        if array[j+1] < array[j]: swap(array, j, j+1)

def swap(a, i, j): a[i], a[j] = a[j], a[i]

def print_array(a):
    n = len(a)
    for i in range(n): print(a[i], end = ' ')
    print_newline()
    
def print_newline(): print()

main()

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

Выделять код в отдельную функцию рекомендуется как минимум в следующих случаях:

  • функция стала слишком длинной: даже 30-40 строк — это уже очень много; в таком случае её код нужно представить в виде нескольких вызовов вспомогательных функций
  • код слишком сильно уехал вправо (например, из-за условия внутри условия или цикла внутри цикла); вообще длинные строчки или же вложенные управляющие конструкции очень трудно читать и понимать их назначение
  • в нескольких местах появись похожие друг на друга участки кода: их следует обобщить, приведя к одинаковому виду, и представить в виде вызовов одной и той же функции
  • полезно писать функции-тесты, содержащие примеры использования других функций и ожидаемые от них результаты; функции-тесты являются одновременно комментарием к функции и некоторой гарантией того, что изменение реализации функции не приведёт к неожиданным изменениям её поведения

Комментарии

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

  • имеет тенденцию устаревать, не успевая за изменениями кода
  • из-за особенностей естественных языков может иметь множество разнообразных интерпретаций

Ни в коем случае не нужно делать комментарии в духе:

a += 3  ## прибавляем тройку

А вот пример более удачного комментария:

x1 = average(distribution, segment) + 3  ## прибавляем тройку ...
x2 = approximate('quadratic', foobar, x1)
x3 = adjust(y, x1, x2)
## ... поскольку в статье [Х. Суньвчай, И. Выньсухим, 1994] так опредён 
## первый шаг аппроксимационного алгоритма

или

foobar(a+3)  ## прибавляем тройку для большей надёжности

Говоря другими словами, комментарий должен отвечать не на вопрос «Что тут происходит?» (на этот вопрос отвечает сам код), а на вопрос «Зачем так происходит?»

Ввод/вывод

Скоро будет

Трюки

Скоро будет