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