Эта глава посвящена функциям — именованным блокам кода, предназначенным для решения одной конкретной задачи. Чтобы выполнить задачу, определенную в виде функции, вы вызываете функцию, отвечающую за эту задачу. Если задача должна многократно выполняться в программе, то вам не придется заново вводить весь необходимый код; просто вызовите функцию, предназначенную для решения задачи, и благодаря этому вызову Python получит указание выполнить код, содержащийся внутри функции. Как вы вскоре убедитесь, использование функций упрощает чтение, написание, тестирование кода и исправление ошибок.
В этой главе также рассматриваются возможности передачи информации функциям. Вы узнаете, как писать функции, основной задачей которых является вывод информации, и другие функции, предназначенные для обработки данных и возвращения значения (или набора значений). Наконец, вы научитесь хранить функции в отдельных файлах, называемых модулями, в целях упорядочения файлов основной программы.
Вот простая функция greet_user (), которая выводит приветствие:
greeter.py
def greet_user():
"""Выводит простое приветствие."""
print("Hello!")
greet_user()
В этом примере представлена простейшая структура функции. Первая строка с помощью ключевого слова def сообщает Python, что вы определяете функцию. В определении функции (function definition) указываются имя функции и, если необходимо, описание информации, требуемой функции для решения ее задачи. Эта информация заключается в круглые скобки. В данном примере функции присвоено имя greet_user(), и она не нуждается в дополнительной информации для решения своей задачи, поэтому круглые скобки пусты. (Но даже в этом случае они обязательны.) Наконец, определение завершается двоеточием.
Все строки с отступами, следующие за def greet_user():, образуют тело функции. Текст во второй строке представляет собой комментарий — строку документации с описанием действий функции (строк может быть несколько). Такие комментарии заключаются в тройные кавычки; Python опознает их по этой последовательности символов во время генерирования документации к функциям в ваших программах.
«Настоящий» код в теле этой функции состоит всего из одной строки print("Hello!"). Таким образом, функция greet_user() решает всего одну задачу: выполнение функции print("Hello!").
Когда потребуется использовать эту функцию, вызовите ее. Так Python получит указание выполнить содержащийся в ней код. Чтобы вызвать функцию, укажите ее имя, за которым следует вся необходимая информация, заключенная в круглые скобки. Никакая дополнительная информация не нужна, поэтому вызов функции эквивалентен простому выполнению функции greet_user(). Как и ожидалось, функция выводит сообщение Hello!:
Hello!
Если внести небольшие изменения, то с помощью функции greet_user() вы сможете поприветствовать пользователя, назвав его по имени. Для этого следует добавить имя username в круглых скобках в определение функции def greet_user(). Благодаря добавлению username функция примет любое значение, которое будет заключено в скобки при вызове. Теперь функция ожидает, что при каждом вызове будет передаваться имя пользователя. При вызове greet_user() укажите имя (например, 'jesse') в круглых скобках:
def greet_user(username):
"""Выводит простое приветствие."""
print(f"Hello, {username.title()}!")
greet_user('jesse')
Команда greet_user('jesse') вызывает функцию greet_user() и передает ей информацию, необходимую для выполнения вызова функции print(). Функция получает переданное имя и выводит приветствие для этого имени:
Hello, Jesse!
Точно так же команда greet_user('sarah') вызывает функцию greet_user() и передает ей строку 'sarah', в результате чего будет выведено сообщение Hello, Sarah! Функцию greet_user() можно вызвать сколько угодно раз и передать ей любое имя на ваше усмотрение — и вы будете получать ожидаемый результат.
Функция greet_user() определена так, что для работы она должна получить значение переменной username. После того как функция будет вызвана и получит необходимую информацию (имя пользователя), она выведет правильное приветствие.
Переменная username в определении greet_user() — параметр, то есть условные данные, необходимые функции для выполнения ее работы. Значение 'jesse' в greet_user('jesse') — аргумент, то есть конкретная информация, переданная при вызове функции. Вызывая функцию, вы заключаете значение, с которым функция должна работать, в круглые скобки. В данном случае аргумент 'jesse' был передан функции greet_user(), а его значение было сохранено в переменной username.
ПРИМЕЧАНИЕ
Иногда в литературе термины «аргумент» и «параметр» используются как синонимы. Не удивляйтесь, если переменные в определении функции вдруг будут названы аргументами, а значения, переданные при вызове функции, — параметрами.
Упражнения
8.1. Сообщение. Напишите функцию display_message() для вывода сообщения по теме, рассматриваемой в этой главе. Вызовите функцию и убедитесь в том, что сообщение выводится правильно.
8.2. Любимая книга. Напишите функцию favorite_book(), которая получает один параметр title. Функция должна выводить сообщение вида «Одна из моих любимых книг — “Алиса в стране чудес”». Вызовите функцию и убедитесь в том, что название книги правильно передается как аргумент при вызове функции.
Определение функции может иметь несколько параметров, поэтому может оказаться, что при вызове функции должны передаваться несколько аргументов. Существует несколько способов передачи аргументов функциям. Позиционные аргументы перечисляются в порядке, точно соответствующем порядку записи параметров; именованные аргументы состоят из имени переменной и значения; наконец, существуют списки и словари значений. Рассмотрим все эти способы.
При вызове функции каждый аргумент должен быть сопоставлен с параметром в определении функции. Проще всего сделать это на основании порядка перечисления аргументов. Значения, связываемые с аргументами подобным образом, называются позиционными аргументами (positional arguments).
Чтобы понять, как работает эта схема, рассмотрим функцию для вывода информации о домашних животных. Функция сообщает тип животного и его имя:
pets.py
❶ def describe_pet(animal_type, pet_name):
"""Выводит информацию о животном."""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
❷ describe_pet('hamster', 'harry')
Из определения видно, что функции должны передаваться тип животного (animal_type) и его имя (pet_name) ❶. При вызове describe_pet() необходимо передать тип и имя — именно в таком порядке. В этом примере аргумент 'hamster' сохраняется в параметре animal_type, а аргумент 'harry' — в параметре pet_name ❷. В теле функции эти два параметра используются для вывода информации о питомце — хомяке Гарри:
I have a hamster.
My hamster's name is Harry.
Функция может вызываться в программе столько раз, сколько потребуется. Для вывода информации о другом животном достаточно одного вызова describe_pet():
def describe_pet(animal_type, pet_name):
"""Выводит информацию о животном."""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet('hamster', 'harry')
describe_pet('dog', 'Willie')
Во втором вызове функции describe_pet() передаются аргументы 'dog' и 'willie'. По аналогии с предыдущей парой аргументов Python сопоставляет аргумент 'dog' с параметром animal_type, а аргумент 'Willie' с параметром pet_name. Как и в предыдущем случае, функция выполняет свою задачу, однако на этот раз выводятся другие значения. Теперь у нас есть хомяк по имени Гарри и собака по имени Вилли:
I have a hamster.
My hamster's name is Harry.
I have a dog.
My dog's name is Willie.
Многократный вызов функции — чрезвычайно эффективный механизм. Код вывода информации о домашнем животном пишется один раз в функции. Каждый раз, когда вам понадобится вывести информацию о новом животном, вы вызываете функцию с данными нового животного. Даже если код вывода информации разрастется до 10 строк, вы все равно сможете вывести информацию с помощью всего одной команды — для этого достаточно снова вызвать функцию.
Если нарушить порядок следования аргументов в вызове при использовании позиционных аргументов, возможны неожиданные результаты:
def describe_pet(animal_type, pet_name):
"""Выводит информацию о животном."""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet('harry', 'hamster')
В этом вызове функции сначала передается имя, а затем тип животного. Аргумент 'harry' находится в первой позиции, поэтому значение сохраняется в параметре animal_type, а аргумент 'hamster' — в параметре pet_name. На этот раз вывод получается бессмысленным:
I have a harry.
My harry's name is Hamster.
Если вы получили подобные странные результаты, то проверьте, что порядок следования аргументов в вызове функции соответствует порядку параметров в ее определении.
Именованный аргумент (keyword argument) представляет собой пару «имя — значение», передаваемую функции. Имя и значение связываются с аргументом напрямую, так что при передаче аргумента путаница с порядком исключается (вы не увидите в выводе «моего гарри зовут Хомяк»). Именованные аргументы избавляют от хлопот с порядком аргументов при вызове функции, а также проясняют роль каждого значения в вызове функции.
Перепишем программу pets.py с использованием именованных аргументов при вызове describe_pet():
def describe_pet(animal_type, pet_name):
"""Выводит информацию о животном."""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet(animal_type='hamster', pet_name='harry')
Функция describe_pet() не изменилась. Однако на этот раз при вызове функции мы явно сообщаем Python, с каким параметром должен быть связан каждый аргумент. При обработке вызова функции Python знает, что аргумент 'hamster' должен быть сохранен в параметре animal_type, а аргумент 'harry' — в параметре pet_name.
Порядок следования именованных аргументов в данном случае неважен, поскольку Python знает, где должно храниться каждое значение. Следующие два вызова функции эквивалентны:
describe_pet(animal_type='hamster', pet_name='harry')
describe_pet(pet_name='harry', animal_type='hamster')
ПРИМЕЧАНИЕ
При использовании именованных аргументов будьте внимательны — имена должны точно совпадать с именами параметров из определения функции.
Для каждого параметра вашей функции можно определить значение по умолчанию (default value). Если при вызове функции передается аргумент, соответствующий данному параметру, то Python использует значение аргумента, а если нет — значение по умолчанию. Таким образом, если для параметра определено значение по умолчанию, то вы можете опустить соответствующий аргумент, который обычно добавляется в вызов функции. Значения по умолчанию упрощают вызовы функций и проясняют типичные способы использования функций.
Например, если вы заметили, что большинство вызовов describe_pet() используется для описания собак, то задайте animal_type значение по умолчанию 'dog'. Теперь в любом вызове describe_pet() для собаки эту информацию можно опустить:
def describe_pet(pet_name, animal_type='dog'):
"""Выводит информацию о животном."""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet(pet_name='Willie')
Мы изменили определение describe_pet() и добавили для параметра animal_type значение по умолчанию 'dog'. Если теперь функция будет вызвана без указания animal_type, то Python знает, что для этого параметра следует использовать значение 'dog':
I have a dog.
My dog's name is Willie.
Обратите внимание: в определении функции пришлось изменить порядок параметров. Благодаря значению по умолчанию указывать аргумент с типом животного необязательно, поэтому единственным оставшимся аргументом в вызове функции остается имя домашнего животного. Python интерпретирует его как позиционный аргумент, и если функция вызывается только с именем животного, данный аргумент сопоставляется с первым параметром в определении функции. Именно поэтому первым параметром должно быть имя животного.
В простейшем варианте использования этой функции при вызове передается только имя собаки:
describe_pet('Willie')
Вызов функции выводит тот же результат, что и в предыдущем примере. Единственный переданный аргумент 'Willie' сопоставляется с первым параметром в определении — pet_name. Для animal_type аргумент не указан, поэтому Python использует значение по умолчанию — 'dog'.
Для вывода информации о любом другом животном, кроме собаки, используется вызов функции следующего вида:
describe_pet(pet_name='harry', animal_type='hamster')
Аргумент для параметра animal_type задан явно, поэтому Python игнорирует значение параметра по умолчанию.
ПРИМЕЧАНИЕ
Если вы используете значения по умолчанию, то все параметры со значением по умолчанию должны следовать после параметров, у которых значений по умолчанию нет. Это необходимо для того, чтобы Python правильно интерпретировал позиционные аргументы.
Позиционные и именованные аргументы, а также значения по умолчанию могут использоваться одновременно, поэтому часто существует несколько эквивалентных способов вызова функций. Возьмем следующий оператор describe_pets() с одним значением по умолчанию:
def describe_pet(pet_name, animal_type='dog'):
При таком определении аргумент для параметра pet_name должен задаваться в любом случае, но это значение может передаваться как в позиционном, так и в именованном формате. Если описываемое животное не является собакой, то аргумент animal_type тоже должен быть добавлен в вызов, и этот аргумент тоже может быть задан как в позиционном, так и в именованном формате.
Все следующие вызовы являются допустимыми для данной функции:
# Собака Вилли.
describe_pet('Willie')
describe_pet(pet_name='Willie')
# Хомяк Гарри.
describe_pet('harry', 'hamster')
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet(animal_type='hamster', pet_name='harry')
Все вызовы функции выдадут такой же результат, как и в предыдущих примерах.
На самом деле не так важно, какой стиль вызова вы используете. Если ваша функция выдает нужный результат, то выберите тот стиль, который вам кажется более понятным.
Не удивляйтесь, если на первых порах вашей работы с функциями будут встречаться ошибки несоответствия аргументов. Такие ошибки происходят в том случае, если вы передали меньше или больше аргументов, чем необходимо функции для выполнения ее работы. Например, вот что произойдет при попытке вызвать describe_pet() без аргументов:
def describe_pet(animal_type, pet_name):
"""Выводит информацию о животном."""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet()
Python распознает, что при вызове функции часть информации отсутствует, и мы видим это в данных трассировки:
Traceback (most recent call last):
❶ File "pets.py", line 6, in <module>
❷ describe_pet()
^^^^^^^^^^^^^^
❸ TypeError: describe_pet() missing 2 required positional arguments:
'animal_type' and 'pet_name'
В данных трассировки сначала сообщается местонахождение проблемы ❶, чтобы вы поняли, что с вызовом функции что-то пошло не так. Далее приводится вызов функции, приведший к ошибке ❷. И наконец, Python сообщает, что при вызове пропущены два аргумента, и указывает их имена ❸. Если бы функция размещалась в отдельном файле, то, вероятно, вы смогли бы исправить вызов и вам не пришлось бы открывать этот файл и читать код функции.
Python помогает еще и тем, что читает код функции и сообщает имена аргументов, которые необходимо передать при вызове. Это еще одна причина присваивать переменным и функциям описательные имена. В этом случае сообщения об ошибках Python принесут больше пользы и вам, и любому другому разработчику, который будет использовать ваш код.
Если при вызове будут переданы лишние аргументы, то вы получите похожую трассировку, которая поможет привести вызов функции в соответствие с ее определением.
Упражнения
8.3. Футболка. Напишите функцию make_shirt(), которая получает размер футболки и текст, который должен быть напечатан на ней. Функция должна выводить сообщение с размером и текстом.
Вызовите функцию с помощью позиционных аргументов. Вызовите функцию во второй раз с помощью именованных аргументов.
8.4. Большие футболки. Измените функцию make_shirt(), чтобы футболки по умолчанию имели размер L и на них выводился текст «Я люблю Python». Создайте футболку с размером L и текстом по умолчанию, а также футболку любого размера с другим текстом.
8.5. Города. Напишите функцию describe_city(), которая получает названия города и страны. Функция должна выводить простое сообщение (например, «Рейкьявик находится в Исландии»). Задайте параметру страны значение по умолчанию. Вызовите свою функцию для трех разных городов, по крайней мере один из которых не находится в стране по умолчанию.
Функция не обязана выводить результаты своей работы напрямую. Вместо этого она может обработать данные, а затем вернуть значение или набор сообщений. Значение, возвращаемое функцией, называется возвращаемым значением (return value). Оператор return передает значение из функции в точку программы, в которой эта функция была вызвана. Возвращаемые значения помогают переместить большую часть рутинной работы в вашей программе в функции, чтобы упростить основной код программы.
Рассмотрим функцию, которая получает имя и фамилию и возвращает отформатированное полное имя:
formatted_name.py
def get_formatted_name(first_name, last_name):
"""Возвращает отформатированное полное имя."""
❶ full_name = f"{first_name} {last_name}"
❷ return full_name.title()
❸ musician = get_formatted_name('jimi', 'hendrix')
print(musician)
Определение get_formatted_name() получает в параметрах имя и фамилию. Функция объединяет эти два имени, добавляет между ними пробел и сохраняет результат в full_name ❶. Значение full_name преобразуется: начальные буквы переводятся в верхний регистр, — а затем возвращается в строку вызова ❷.
Вызывая функцию, которая возвращает значение, необходимо предоставить переменную, в которой должно храниться это значение. В данном случае оно записывается в переменную musician ❸. Результат содержит отформатированное полное имя, созданное из имени и фамилии:
Jimi Hendrix
Может показаться, что все эти хлопоты излишни — с таким же успехом можно было использовать команду:
print("Jimi Hendrix")
Но если представить, что вы пишете большую программу, в которой многочисленные имена и фамилии должны храниться по отдельности, то такие функции, как get_formatted_name(), становятся чрезвычайно полезными. Вы храните имена отдельно от фамилий, а затем вызываете функцию везде, где потребуется вывести полное имя.
Иногда бывает удобно сделать аргумент необязательным, чтобы разработчик, использующий функцию, мог передать дополнительную информацию только в том случае, если захочет этого. Чтобы сделать аргумент необязательным, можно воспользоваться значением по умолчанию.
Допустим, вы захотели расширить функцию get_formatted_name(), чтобы она работала и со вторыми именами. Первая попытка могла бы выглядеть так:
def get_formatted_name(first_name, middle_name, last_name):
"""Возвращает отформатированное полное имя."""
full_name = f"{first_name} {middle_name} {last_name}"
return full_name.title()
musician = get_formatted_name('john', 'lee', 'hooker')
print(musician)
Функция работает при получении имени, второго имени и фамилии. Она получает все три части имени, а затем создает из них строку. После этого она добавляет пробелы там, где это уместно, и преобразует полное имя — переводит начальные буквы в верхний регистр (выполняет капитализацию):
John Lee Hooker
Однако вторые имена нужны не всегда, а в такой записи функция не будет работать, если при вызове ей передается только имя и фамилия. Чтобы средний аргумент был необязательным, можно присвоить аргументу middle_name пустое значение по умолчанию; этот аргумент игнорируется, если пользователь не передал для него значение. Чтобы функция get_formatted_name() работала без второго имени, следует назначить для параметра middle_name пустую строку значением по умолчанию и переместить его в конец списка параметров:
def get_formatted_name(first_name, last_name, middle_name=''):
"""Возвращает отформатированное полное имя."""
❶ if middle_name:
full_name = f"{first_name} {middle_name} {last_name}"
❷ else:
full_name = f"{first_name} {last_name}"
return full_name.title()
musician = get_formatted_name('jimi', 'hendrix')
print(musician)
❸ musician = get_formatted_name('john', 'hooker', 'lee')
print(musician)
В этом примере имя создается из трех возможных частей. Поскольку имя и фамилия указываются всегда, эти параметры стоят в начале списка в определении функции. Второе имя необязательно, поэтому находится на последнем месте в определении, а его значением по умолчанию является пустая строка.
В теле функции мы сначала проверяем, было ли задано второе имя. Python интерпретирует непустые строки как истинное значение, и если при вызове задан аргумент второго имени, то middle_name дает результат True ❶. Если второе имя указано, то из имени, второго имени и фамилии создается полное имя. Затем имя подвергается капитализации символов и возвращается в строку вызова функции, где сохраняется в переменной musician и выводится. Если второе имя не указано, то пустая строка не проходит проверку if и выполняет блок else ❷. В этом случае полное имя создается только из имени и фамилии и отформатированное имя возвращается в строку вызова, где сохраняется в переменной musician и выводится.
Вызов этой функции с именем и фамилией достаточно тривиален. Но при использовании второго имени придется проследить за тем, чтобы второе имя было последним из передаваемых аргументов. Это необходимо для правильного связывания позиционных аргументов ❸.
Обновленная версия этой функции подойдет как для людей, у которых задаются только имя и фамилия, так и для людей со вторым именем:
Jimi Hendrix
John Lee Hooker
Необязательные значения позволяют функциям работать в максимально широком спектре сценариев использования, не усложняя вызовы.
Функция может вернуть любое значение, которое вам потребуется, в том числе и более сложную структуру данных (например, список или словарь). Так, следующая функция получает части имени и возвращает словарь, представляющий человека:
person.py
def build_person(first_name, last_name):
"""Возвращает словарь с информацией о человеке."""
❶ person = {'first': first_name, 'last': last_name}
❷ return person
musician = build_person('jimi', 'hendrix')
❸ print(musician)
Функция build_person() получает имя и фамилию и сохраняет полученные значения в словаре ❶. Значение first_name сохраняется с ключом 'first', а значение last_name — с ключом 'last'. Затем весь словарь с описанием человека возвращается ❷. Значение выводится ❸ с двумя исходными фрагментами текстовой информации, теперь хранящимися в словаре:
{'first': 'jimi', 'last': 'hendrix'}
Функция получает простую текстовую информацию и помещает ее в более удобную структуру данных, которая позволяет работать с информацией (помимо простого вывода). Строки 'jimi' и 'hendrix' теперь помечены как имя и фамилия. Функцию можно легко расширить так, чтобы она принимала дополнительные значения — второе имя, возраст, профессию или любую другую информацию о человеке, которую вы хотите сохранить. Например, следующее изменение позволяет сохранить возраст человека:
def build_person(first_name, last_name, age=''):
"""Возвращает словарь с информацией о человеке."""
person = {'first': first_name, 'last': last_name}
if age:
person['age'] = age
return person
musician = build_person('jimi', 'hendrix', age=27)
print(musician)
В определение функции добавляется новый необязательный параметр age, которому присваивается специальное значение по умолчанию None — оно используется для переменных, которым не присвоено никакое значение. Можно рассматривать None как значение-заполнитель. При проверке условий None интерпретируется как False. Если вызов функции содержит значение параметра age, то оно сохраняется в словаре. Функция всегда сохраняет имя, но ее можно изменить, чтобы она сохраняла любую необходимую информацию о человеке.
Функции могут использоваться со всеми структурами Python, уже известными вам. Например, задействуем функцию get_formatted_name() в цикле while, чтобы поприветствовать пользователей более официально. Первая версия программы, обращающейся к ним по имени и фамилии, может выглядеть так:
greeter.py
def get_formatted_name(first_name, last_name):
"""Возвращает отформатированное полное имя."""
full_name = f"{first_name} {last_name}"
return full_name.title()
# Бесконечный цикл!
while True:
❶ print("\nPlease tell me your name:")
f_name = input("First name: ")
l_name = input("Last name: ")
formatted_name = get_formatted_name(f_name, l_name)
print(f"\nHello, {formatted_name}!")
В этом примере приводится простая версия get_formatted_name(), не использующая вторые имена. В цикле while имя и фамилия пользователя запрашиваются по отдельности ❶.
Но у этого цикла while есть один недостаток: в нем не определено условие завершения. Где следует поместить условие завершения при запросе серии данных? Пользователю нужно предоставить возможность выйти из цикла как можно раньше, так что в приглашении должен содержаться способ завершения. Оператор break позволяет немедленно прервать цикл при запросе любого из компонентов:
def get_formatted_name(first_name, last_name):
"""Возвращает отформатированное полное имя."""
full_name = f"{first_name} {last_name}"
return full_name.title()
while True:
print("\nPlease tell me your name:")
print("(enter 'q' at any time to quit)")
f_name = input("First name: ")
if f_name == 'q':
break
l_name = input("Last name: ")
if l_name == 'q':
break
formatted_name = get_formatted_name(f_name, l_name)
print(f"\nHello, {formatted_name}!")
В программу добавляется сообщение, которое объясняет пользователю, как завершить ввод данных, и при вводе признака завершения в любом из приглашений цикл прерывается. Теперь программа будет приветствовать пользователя до тех пор, пока вместо имени или фамилии не будет введен символ 'q':
Please tell me your name:
(enter 'q' at any time to quit)
First name: eric
Last name: matthes
Hello, Eric Matthes!
Please tell me your name:
(enter 'q' at any time to quit)
First name: q
Упражнения
8.6. Названия городов. Напишите функцию city_country(), которая получает название города и страну. Функция должна возвращать строку в формате:
"Santiago, Chile"
Вызовите свою функцию по крайней мере для трех пар «город — страна» и выведите возвращенное значение.
8.7. Альбом. Напишите функцию make_album(), которая создает словарь с описанием музыкального альбома. Функция должна получать имя исполнителя и название альбома и возвращать словарь, содержащий эти два вида информации. Используйте функцию для создания трех словарей, представляющих разные альбомы. Выведите все возвращаемые значения, чтобы показать, что информация правильно сохраняется во всех трех словарях.
Добавьте в make_album() дополнительный параметр для сохранения количества дорожек в альбоме, имеющий значение по умолчанию None. Если в строке вызова есть значение количества дорожек, то добавьте это значение в словарь альбома. Создайте как минимум один новый вызов функции, который передает количество дорожек в альбоме.
8.8. Пользовательские альбомы. Начните с программы из упражнения 8.7. Напишите цикл while, в котором пользователь вводит данные об исполнителе и название альбома. Затем в цикле вызывается функция make_album() для введенных пользователем данных и выводится созданный словарь. Не забудьте предусмотреть признак завершения в цикле while.
Часто при вызове функции удобно передать список — имен, чисел или более сложных объектов (например, словарей). При передаче списка функция получает прямой доступ ко всему его содержимому. Мы воспользуемся функциями для того, чтобы сделать работу со списком более эффективной.
Допустим, вы хотите вывести приветствие для каждого пользователя из списка. В следующем примере список имен передается функции greet_users(), которая выводит приветствие для каждого пользователя по отдельности:
greet_users.py
def greet_users(names):
"""Выводит простое приветствие для каждого пользователя в списке."""
for name in names:
msg = f"Hello, {name.title()}!"
print(msg)
usernames = ['hannah', 'ty', 'margot']
greet_users(usernames)
В соответствии со своим определением функция greet_users() рассчитывает получить список имен, который сохраняется в параметре names. Функция перебирает полученный список и выводит приветствие для каждого пользователя. Вне функции мы определяем список пользователей usernames, который затем передается greet_users() в вызове функции:
Hello, Hannah!
Hello, Ty!
Hello, Margot!
Результат выглядит именно так, как ожидалось. Каждый пользователь получает персональное сообщение, и эту функцию можно вызвать для любого нового набора пользователей.
Если вы передаете список функции, то код функции сможет изменить список. Все изменения, внесенные в список в теле функции, являются постоянными, что позволяет эффективно работать со списком даже при больших объемах данных.
Допустим, компания печатает на 3D-принтере модели, предоставленные пользователем. Проекты хранятся в списке, а после печати перемещаются в отдельный список. В следующем примере приведена реализация, не использующая функции:
printing_models.py
# Список моделей, которые необходимо напечатать.
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
# Цикл последовательно печатает каждую модель до конца списка.
# После печати каждая модель перемещается в список completed_models.
while unprinted_designs:
current_design = unprinted_designs.pop()
print(f"Printing model: {current_design}")
completed_models.append(current_design)
# Вывод готовых моделей.
print("\nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)
В начале программы создается список моделей и пустой список completed_models, в который каждая модель перемещается после печати. Пока в unprinted_designs остаются модели, цикл while имитирует печать каждой модели: текущая модель удаляется с конца списка, сохраняется в current_design, а пользователь получает сообщение о том, что она была напечатана. Затем модель перемещается в список напечатанных. После завершения цикла выводится список напечатанных моделей:
Printing model: dodecahedron
Printing model: robot pendant
Printing model: phone case
The following models have been printed:
dodecahedron
robot pendant
phone case
Мы можем изменить структуру кода: для этого следует написать две функции, каждая из которых решает одну конкретную задачу. Бо́льшая часть кода останется неизменной; просто программа становится более эффективной. Первая функция занимается печатью, а вторая выводит сводку напечатанных моделей:
❶ def print_models(unprinted_designs, completed_models):
"""
Имитирует печать моделей, пока список не станет пустым.
Каждая модель после печати перемещается в completed_models.
"""
while unprinted_designs:
current_design = unprinted_designs.pop()
print(f"Printing model: {current_design}")
completed_models.append(current_design)
❷ def show_completed_models(completed_models):
"""Выводит информацию обо всех напечатанных моделях."""
print("\nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)
Сначала определяется функция print_models() с двумя параметрами: список моделей для печати и список готовых моделей ❶. С этими двумя списками функция имитирует печать каждой модели, последовательно извлекая модели из первого списка и перемещая их во второй. Затем определяется функция show_completed_models() с одним параметром: списком напечатанных моделей ❷. Она получает этот список и выводит имена всех напечатанных моделей.
Программа выводит тот же результат, что и версия без функций, но структура кода значительно улучшилась. Код, выполняющий бо́льшую часть работы, разнесен по двум разным функциям; это упрощает чтение основной части программы. Теперь любому разработчику будет намного проще просмотреть код программы и понять, что она делает:
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)
Программа создает список моделей для печати и пустой список для готовых моделей. Затем, поскольку обе функции уже определены, остается вызвать их и передать правильные аргументы. Мы вызываем print_models() и передаем два необходимых списка; как и ожидалось, эта функция имитирует печать моделей. Затем вызывается функция show_completed_models(), и ей передается список готовых моделей, чтобы она могла вывести информацию о напечатанных моделях. Благодаря описательным именам функций другой разработчик сможет прочитать этот код и понять его даже без комментариев.
Вдобавок эта программа создает меньше проблем с расширением и сопровождением, чем версия без функций. Если позднее потребуется напечатать новую партию моделей, то достаточно снова вызвать print_models(). Если окажется, что код печати необходимо модифицировать, то изменения можно внести в одном месте, и они автоматически распространятся на все вызовы функции. Такой подход намного эффективнее независимой правки кода в нескольких местах программы.
В этом примере также демонстрируется принцип, в соответствии с которым каждая функция должна решать одну конкретную задачу. Первая функция печатает каждую модель, а вторая выводит информацию о готовых моделях. Такой подход предпочтительнее решения обеих задач в функции. Если вы пишете функцию и видите, что она решает слишком много разных задач, то попробуйте разделить ее код на две функции. Помните, что функции всегда можно вызывать из других функций. Эта возможность может пригодиться для разбиения сложных задач на серию составляющих.
Иногда требуется предотвратить изменение списка в функции. Допустим, у вас есть список моделей для печати, и вы пишете функцию для перемещения их в список готовых моделей, как в предыдущем примере. Возможно, даже после печати всех моделей исходный список нужно оставить для отчетности. Но поскольку все имена моделей были перенесены из списка unprinted_designs, остался только пустой список; исходная версия списка потеряна. Проблему можно решить, передав функции копию списка вместо оригинала. В этом случае все изменения, которые она вносит в список, будут распространяться только на копию, а оригинал остается неизменным.
Чтобы передать функции копию списка, можно поступить так:
имя_функции(имя_списка[:])
Синтаксис среза [:] создает копию списка для передачи функции. Если удаление элементов из списка unprinted_designs в программе print_models.py нежелательно, то функцию print_models() можно вызвать так:
print_models(unprinted_designs[:], completed_models)
Функция print_models() может выполнить свою работу, поскольку все равно получает имена всех ненапечатанных моделей. Однако на этот раз она использует не сам список unprinted_designs, а его копию. Список completed_models заполняется именами напечатанных моделей, как и в предыдущем случае, но исходный список функция не изменяет.
Несмотря на то что передача копии позволяет сохранить содержимое списка, обычно функциям следует передавать исходный список (если у вас нет веских причин для передачи копии). Работа с существующим списком более эффективна, поскольку программе не приходится тратить время и память на создание отдельной копии (лишние затраты особенно заметны при работе с большими списками).
Упражнения
8.9. Сообщения. Создайте список с серией коротких сообщений. Передайте список функции show_messages(), которая выводит текст каждого сообщения в списке.
8.10. Отправка сообщений. Начните с копии вашей программы из упражнения 8.9. Напишите функцию send_messages(), которая выводит каждое сообщение и перемещает его в новый список sent_messages. После вызова функции выведите оба списка и убедитесь в том, что перемещение прошло успешно.
8.11. Архивированные сообщения. Начните с программы из упражнения 8.10. Вызовите функцию send_messages(), чтобы создать копию списка сообщений. После вызова функции выведите оба списка и убедитесь в том, что в исходном списке остались все сообщения.
В некоторых ситуациях вы не знаете заранее, сколько аргументов должно быть передано функции. К счастью, Python позволяет ей получить произвольное количество аргументов из вызывающего оператора.
Для примера рассмотрим функцию для создания пиццы. Она должна получить набор начинок для пиццы, но вы не знаете заранее, сколько начинок закажет клиент. Функция в следующем примере получает один параметр *toppings, но он объединяет все аргументы, заданные в вызывающей строке:
pizza.py
def make_pizza(*toppings):
"""Выводит список заказанных начинок."""
print(toppings)
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')
Благодаря звездочке в имени параметра *toppings Python получает указание создать пустой кортеж toppings и упаковать в него все полученные значения. Результат вызова print() в теле функции показывает, что Python успешно вызывает обе функции: и с одним значением, и с тремя. Разные вызовы обрабатываются похожим образом. Обратите внимание: Python упаковывает аргументы в кортеж даже в том случае, если функция получает всего одно значение:
('pepperoni',)
('mushrooms', 'green peppers', 'extra cheese')
Теперь вызов функции print() можно заменить циклом, который перебирает список начинок и выводит описание заказанной пиццы:
def make_pizza(*toppings):
"""Выводит описание пиццы."""
print("\nMaking a pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')
Функция реагирует соответственно, независимо от того, сколько значений получила — одно или три:
Making a pizza with the following toppings:
- pepperoni
Making a pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese
Этот синтаксис работает независимо от количества аргументов, переданных функции.
Если вы хотите, чтобы функция могла вызываться с разным количеством аргументов, то параметр для получения произвольного количества аргументов должен стоять на последнем месте в определении функции. Python сначала подбирает соответствия для позиционных и именованных аргументов, а затем объединяет все остальные аргументы в последнем параметре.
Например, если функция должна получать размер пиццы, то данный параметр должен стоять в списке до параметра *toppings:
def make_pizza(size, *toppings):
"""Выводит описание пиццы."""
print(f"\nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
В определении функции Python сохраняет первое полученное значение в параметре size. Все остальные значения, следующие за ним, сохраняются в кортеже toppings. В вызовах функций на первом месте располагается аргумент для параметра size, а за ним следует сколько угодно начинок.
В итоге для каждой пиццы указываются размер и количество начинок, и каждый фрагмент информации выводится в положенном месте: сначала размер, а потом начинки:
Making a 16-inch pizza with the following toppings:
- pepperoni
Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese
ПРИМЕЧАНИЕ
В программах часто используется имя обобщенного параметра *args, который служит для хранения произвольного набора позиционных аргументов.
Иногда программа должна получать произвольное количество аргументов, но вы не знаете заранее, какая информация будет передаваться функции. В таких случаях можно написать функцию, получающую столько пар «ключ — значение», сколько указано в вызывающем операторе. Один из возможных примеров — создание пользовательских профилей: вы знаете, что получите информацию о пользователе, но заранее неизвестно, какую именно. Функция build_profile() в следующем примере всегда получает имя и фамилию, но может получать и произвольное количество именованных аргументов:
user_profile.py
def build_profile(first, last, **user_info):
"""Создает словарь, содержащий информацию о пользователе."""
❶ user_info['first_name'] = first
user_info['last_name'] = last
return user_info
user_profile = build_profile('albert', 'einstein',
location='princeton',
field='physics')
print(user_profile)
Определение build_profile() ожидает получить имя и фамилию пользователя, а также позволяет передать любое количество пар «имя — значение». Две звездочки перед параметром **user_info заставляют Python создать пустой словарь user_info и упаковать в него все полученные пары «имя — значение». Внутри функции вы можете обращаться к парам «имя — значение» из user_info точно так же, как в любом словаре.
В теле build_profile() в словарь user_info добавляются имя и фамилия, поскольку эти два значения всегда передаются пользователем ❶ и они еще не были помещены в словарь. Затем словарь user_info возвращается в точку вызова функции.
Вызовем функцию build_profile() и передадим ей имя 'albert', фамилию 'einstein' и еще две пары «ключ — значение»: location='princeton' и field='physics'. Программа сохраняет возвращенный словарь в user_profile и выводит его содержимое:
{'location': 'princeton', 'field': 'physics',
'first_name': 'albert', 'last_name': 'einstein'}
Возвращаемый словарь содержит имя и фамилию пользователя, а в данном случае еще и местонахождение, и область исследований. Функция будет работать, сколько бы дополнительных пар «ключ — значение» ни было передано при вызове функции.
При написании функций допускаются самые разнообразные комбинации позиционных, именованных и произвольных значений. Полезно знать о существовании всех этих типов аргументов, поскольку они часто будут встречаться вам при чтении чужого кода. Только практикуясь, вы научитесь правильно использовать разные типы аргументов и поймете, когда следует применять каждый тип; а пока просто используйте самый простой способ, который позволит решить задачу. С опытом вы научитесь выбирать наиболее эффективный вариант для каждой конкретной ситуации.
ПРИМЕЧАНИЕ
В программах часто используется имя обобщенного параметра **kwargs, который служит для хранения произвольного набора ключевых аргументов.
Упражнения
8.12. Бутерброды. Напишите функцию, которая получает список компонентов бутерброда. Функция должна иметь один параметр для любого количества значений, переданных при вызове функции, и выводить описание заказанного бутерброда. Вызовите функцию три раза с разными количествами аргументов.
8.13. Профиль. Начните с копии программы user_profile.py, приведенной в этом подразделе. Создайте собственный профиль с помощью вызова build_profile(), укажите имя, фамилию и три другие пары «ключ — значение» для вашего описания.
8.14. Автомобили. Напишите функцию для сохранения данных об автомобиле в словаре. Она всегда должна возвращать информацию о производителе и названии модели, но при этом может получать произвольное количество именованных аргументов. Вызовите функцию с передачей обязательной информации и еще двух пар «имя — значение» (например, цвет и комплектация). Ваша функция должна работать для вызовов следующего вида:
car = make_car('subaru', 'outback', color='blue', tow_package=True)
Выведите возвращаемый словарь и убедитесь в том, что вся информация была сохранена правильно.
Одно из преимуществ функций заключается в том, что они отделяют блоки кода от основной программы. Если для функций были выбраны описательные имена, то вашу программу будет намного проще читать. Вы можете пойти еще дальше и сохранить функции в отдельном файле, называемом модулем, а затем импортировать модуль в свою программу. Оператор import сообщает Python, что код модуля должен быть доступен в текущем выполняемом программном файле.
Хранение функций в отдельных файлах позволяет скрыть второстепенные детали кода и сосредоточиться на логике более высокого уровня. Кроме того, функции можно использовать во множестве разных программ. Функции, хранящиеся в отдельных файлах, можно передать другим программистам, не распространяя полный код программы. А умение импортировать функции позволит вам использовать библиотеки функций, написанные другими программистами.
Существует несколько способов импортирования модулей; все они кратко рассматриваются ниже.
Чтобы заняться импортированием функций, сначала необходимо создать модуль. Это файл с расширением .py, содержащий код, который вы хотите импортировать в свою программу. Создадим модуль с функцией make_pizza(). Для этого из файла pizza.py следует удалить все, кроме функции make_pizza():
pizza.py
def make_pizza(size, *toppings):
"""Выводит описание пиццы."""
print(f"\nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
Теперь создадим отдельный файл making_pizzas.py в одном каталоге с pizza.py. Файл импортирует только что созданный модуль, а затем дважды вызывает функцию make_pizza():
making_pizzas.py
import pizza
❶ pizza.make_pizza(16, 'pepperoni')
pizza.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
В процессе обработки этого файла благодаря строке import pizza Python получает указание открыть файл pizza.py и скопировать все функции из него в программу. Вы не видите, как происходит копирование, поскольку Python копирует код незаметно для пользователя, пока выполняется программа. Вам необходимо знать одно: любая функция, определенная в pizza.py, будет доступна в making_pizzas.py.
Чтобы вызвать функцию из импортированного модуля, введите имя модуля (pizza), точку и имя функции (make_pizza()) ❶. Код выдает тот же результат, что и исходная программа, в которой модуль не импортировался:
Making a 16-inch pizza with the following toppings:
- pepperoni
Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese
Первый способ импортирования, при котором пишется оператор import с именем модуля, открывает доступ программе ко всем функциям из модуля. Если вы используете эту разновидность оператора import для импортирования всего модуля имя_модуля.py, то каждая функция модуля будет доступна в следующем синтаксисе:
имя_модуля.имя_функции()
Общий синтаксис для импортирования конкретной функции из модуля выглядит так:
from имя_модуля import имя_функции
Вы можете импортировать любое количество функций из модуля, разделив их имена запятыми:
from имя_модуля import функция_0, функция_1, функция_2
Если ограничиться импортированием лишь той функции, которую вы намереваетесь использовать, то пример making_pizzas.py будет выглядеть так:
from pizza import make_pizza
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
При таком синтаксисе использовать точечную нотацию (dot notation) при вызове функции необязательно. Функция make_pizza() явно импортируется в операторе import, поэтому при использовании ее можно вызывать прямо по имени.
Если имя импортируемой функции может конфликтовать с именем существующей или является слишком длинным, то его можно заменить коротким уникальным псевдонимом (alias) — альтернативным именем для функции. Псевдоним назначается функции при импортировании.
В следующем примере функции make_pizza() назначается псевдоним mp(), для чего при импортировании используется конструкция make_pizza as mp. Ключевое слово as переименовывает функцию, используя указанный псевдоним:
from pizza import make_pizza as mp
mp(16, 'pepperoni')
mp(12, 'mushrooms', 'green peppers', 'extra cheese')
Оператор import в этом примере назначает функции make_pizza() псевдоним mp() для этой программы. Каждый раз, когда потребуется вызвать make_pizza(), достаточно добавить вызов mp() — Python выполнит код make_pizza(), избегая конфликтов с другой функцией make_pizza(), которую вы могли добавить в этот файл программы.
Общий синтаксис назначения псевдонима выглядит так:
from имя_модуля import имя_функции as псевдоним
Псевдоним можно назначить для всего модуля. Назначение короткого имени для модуля — скажем, p для pizza — позволит вам быстрее вызывать функции модуля. Вызов p.make_pizza() получается более компактным, чем pizza.make_pizza():
import pizza as p
p.make_pizza(16, 'pepperoni')
p.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
Модулю pizza в операторе import назначается псевдоним p, но все функции модуля сохраняют свои исходные имена. Вызов функций в записи p.make_pizza() не только компактнее pizza.make_pizza(), но и отвлекает внимание от имени модуля и помогает сосредоточиться на описательных именах функций. Благодаря этим именам, четко показывающим, что делает каждая функция, ваш код читать намного удобнее, чем если бы вы использовали полное имя модуля.
Общий синтаксис выглядит так:
import имя_модуля as псевдоним
Можно дать Python указание импортировать каждую функцию в модуле; для этого используется оператор *:
from pizza import *
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
Благодаря звездочке в коде оператора import Python получает указание скопировать каждую функцию из модуля pizza в файл программы. После импортирования всех функций вы сможете вызывать каждую из них по имени, не используя точечную нотацию. Тем не менее лучше не использовать этот способ с большими модулями, написанными другими разработчиками; если модуль содержит функцию, имя которой совпадает с существующим именем из вашего проекта, то возможны неожиданные результаты. Python обнаруживает несколько функций или переменных с одинаковыми именами, и вместо импортирования всех функций по отдельности происходит их замена.
В таких ситуациях лучше всего импортировать только нужную функцию или функции либо импортировать весь модуль и использовать точечную нотацию. При этом создается чистый код, легкочитаемый и понятный. Я добавил этот подраздел только для того, чтобы вы могли распознать подобные операторы import, когда встретите их в чужом коде:
from имя_модуля import *
При форматировании функций необходимо учитывать некоторые подробности. Функции должны иметь описательные имена, состоящие из букв нижнего регистра и символов подчеркивания, — они помогают вам и другим разработчикам понять, что делает ваш код. Эти правила следует соблюдать и при создании имен модулей.
Каждая функция должна быть снабжена комментарием, который кратко поясняет, что она делает. Он должен следовать сразу за определением функции и быть оформлен как строки документации. Если функция хорошо документирована, то другие разработчики смогут использовать ее, прочитав только описание. Конечно, для этого они должны доверять тому, что код работает в соответствии с описанием. Но если разработчики знают имя функции, необходимые ей аргументы и какое значение она возвращает, то смогут использовать ее в своих программах.
Если для параметра задается значение по умолчанию, то слева и справа от знака равенства не должно быть пробелов:
def имя_функции(параметр_0, параметр_1='значение_по_умолчанию')
Те же правила должны применяться для именованных аргументов в вызовах функций:
имя_функции(значение_0, параметр_1='значение')
Документ PEP 8 (https://www.python.org/dev/peps/pep-0008/) рекомендует ограничить длину строк кода 79 символами, чтобы строки были полностью видны в окне редактора нормального размера. Если из-за параметров длина определения функции превышает 79 символов, то нажмите клавишу Enter после открывающей круглой скобки в строке определения. В следующей строке дважды нажмите клавишу Tab, чтобы отделить список аргументов от тела функции, в котором должен быть только один отступ.
Многие редакторы автоматически выравнивают дополнительные строки параметров по отступам, установленным в первой строке:
def имя_функции(
параметр_0, параметр_1, параметр_2,
параметр_3, параметр_4, параметр_5):
тело функции...
Если программа или модуль состоит из нескольких функций, то их можно разделить двумя пустыми строками. Так вам будет проще увидеть, где кончается одна функция и начинается другая.
Все операторы import следует указывать в начале файла. У этого правила есть только одно исключение: файл может начинаться с комментариев, описывающих программу в целом.
Упражнения
8.15. Печать моделей. Выделите функции примера print_models.py в отдельный файл printing_functions.py. Укажите оператор import в начале файла print_models.py и измените файл так, чтобы в нем использовались импортированные функции.
8.16. Импортирование. Возьмите за основу одну из написанных вами программ с одной функцией. Сохраните эту функцию в отдельном файле. Импортируйте ее в файл основной программы и вызовите каждым из следующих способов:
import имя_модуля
from имя_модуля import имя_функции
from имя_модуля import имя_функции as псевдоним
import имя_модуля as псевдоним
from имя_модуля import *
8.17. Форматирование функций. Выберите любые три программы, написанные для этой главы. Убедитесь в том, что в них соблюдаются рекомендации по форматированию, представленные в этом разделе.
В этой главе вы научились писать функции и добавлять аргументы, в которых функциям передается информация, необходимая для их работы. Вы узнали, как использовать позиционные и именованные аргументы и как передать функции произвольное количество аргументов. Вы увидели функции, которые выводят данные, и функции, которые возвращают значения. Вы научились использовать функции со списками, словарями, операторами if и циклами while. Кроме того, вы узнали, как сохранять функции в отдельных файлах, называемых модулями, чтобы код ваших программ стал проще и понятнее. В завершение главы вы изучили рекомендации по форматированию функций, которые помогут вам улучшить структуру ваших программ и упростить их чтение и вами, и другими разработчиками.
Каждый программист должен стремиться к написанию простого кода, который справляется с поставленной задачей, и функции помогают в этом. Вы сможете писать блоки кода и оставлять их на будущее. Если вы знаете, что функция правильно справляется со своей задачей, — считайте, что она работает, и переходите к следующей задаче.
В программе с использованием функций единожды написанный код может заново использоваться сколько угодно раз. Чтобы выполнить код, содержащийся в функции, достаточно написать всего одну строку с вызовом, а функция сделает все остальное. Если же потребуется модифицировать поведение функции, то достаточно внести изменения всего в одном месте; они вступят в силу во всех местах кода, где вызывается эта функция.
Использование функций упрощает чтение ваших программ, а хорошо выбранные имена функций описывают, что делает та или иная часть программы. Прочитав серию вызовов функций, вы гораздо быстрее поймете, что делает функция, чем если бы читали длинную серию программных блоков.
Помимо вышесказанного, функции упрощают тестирование и отладку кода. Когда основная работа программы выполняется с помощью набора функций, каждая из которых решает одну конкретную задачу, вам будет намного проще тестировать и сопровождать ваш код. Напишите отдельную программу, которая вызывает каждую функцию и проверяет ее работоспособность во всех типичных ситуациях. В этом случае вы можете быть уверены в том, что ваши функции всегда работают правильно.
В главе 9 вы научитесь писать классы. Они объединяют функции и данные в один удобный пакет, который можно эффективно использовать для решения разных задач.