Введение
Этот документ — краткая справка. За подробностями лучше обращаться к официальной документации.
Общая структура программы
Программа на языке 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) ## прибавляем тройку для большей надёжности
Говоря другими словами, комментарий должен отвечать не на вопрос «Что тут происходит?» (на этот вопрос отвечает сам код), а на вопрос «Зачем так происходит?»
Ввод/вывод
Скоро будет
Трюки
Скоро будет