16 Интерфейс командной строки. Исключительные ситуации
Для взаимодействия вашей программы с пользователем и другими программами она должна определять интерфейс взаимодействия. В этом модуле мы рассмотрим два способа организации взаимодействия с пользователем: через командную строку и через графические элементы. Кроме того, рассмотрим некоторые продвинутые вещи из стандартного функционала Python.
16.1 Интерфейс командной строки
С интерфейсом командной строки вы регулярно сталкивались в течение первого модуля наших занятий, когда учились работать в собственно командной строке. Для запуска программы, вам необходимо написать путь до неё и передать через пробел параметры запуска. В качестве напоминания слайд из первой лекции Figure 16.1. Для реализации графического интерфейса существуют большое количество библиотек на Python. Самой популярной является argparse, входящей в стандартную библиотеку Python. Также мы рассмотрим библиотеку click.
16.1.1 Как это работает?
При запуске скрипта на Python все переданные аргументы хранятся в специальной переменной argv из модуля sys. Давайте разберем пример из Listing 16.1. По сути запускаемая программа здесь не наш скрипт, а интерпретатор python, а всё остальное является параметрами запуска интерпретатора.
python my_beautiful_script.py meow1 meow2 meow3Поэтому значение sys.argv[0] будет равно пути до нашего скрипта (my_beautiful_script.py), затем все последующие элементы списка sys.argv будут равны параметрам функции (sys.argv[1] = "meow1"; sys.argv[2] = "meow2"; sys.argv[3] = "meow3"). Чтобы обратиться к этой переменной, нуужно сначала импортировать модуль sys. Такой механизм крайне неудобен и не позволяет быстро выстраивать гибкий интерфейс с именами параметрами, проверкой типов и граничных значений параметров. Такой механизм требует для этого огромного количества дополнительного кода, не относящегося к решению задачи, поэтому были разработаны специальные библиотеки.
16.1.2 Argparse
Разберем пример работы с этой библиотекой на примере Listing 16.2. Подробнее с библиотекой вы можете познакомиться на странице документации.
1import argparse
2parser = argparse.ArgumentParser(description = "Построение (Q)SAR моделей с помощью программы MultiPASS.")
3parser.add_argument('-i','--train', help = "Путь к обучающей выборке", required = True, type = str)
4parser.add_argument('output', help = "Путь к выходному файлу", required = True, type = str)
5args = parser.parse_args()
6print(args.input)- 1
- Импортируем библиотеку. Её устанавливать не нужно, в последних версиях (>= 3.2) должна идти в стандартной библиотеке.
- 2
- Создаем объект парсера аргументов
- 3
- Говорим парсеру, какие параметры должен принимать программа. Это пример опционального аргумента. Подробнее о параметрах в Table 16.1
- 4
- Это пример позиционного аргумента.
- 5
- Парсим аргументы командной строки
- 6
- Получить значение аргумента можно прямо по его названию, переданному в парсер.
ArgumentParser принимает следующие полезные параметры:
prog - название программы (default = sys.argv[0]);
usage - описание использования программы (default = генерируется по параметрам);
description - описание работы программы, принципов функционирования и прочая информация, которую вы сочтете нужным указать (default = ““);
epilog - текст, который идет после перечисления описания параметров программы (default = ““);
add_help - добавлять ли автоматически опцию справки как
-h/--help(default = True).
В таблице Table 16.1 перечислены параметры add_argument.
| Параметр | Описание |
|---|---|
| name or flags | Имя или список строк опций, например, ‘foo’ или ‘-f’, ‘–foo’. |
| action | Основной тип действия, выполняемого при обнаружении этого аргумента в командной строке. |
| nargs | Количество аргументов командной строки, которые должны быть переданы как этот параметр. |
| const | Постоянное значение, требуемое некоторыми действиями и выборами nargs. |
| default | Значение, выдаваемое если аргумент отсутствует в командной строке и если он отсутствует в объекте namespace. |
| type | Тип, к которому должен быть преобразован аргумент командной строки. |
| choices | Последовательность допустимых значений для аргумента. |
| required | Может ли опция командной строки быть опущена (только для опциональных). |
| help | Краткое описание того, что делает аргумент. |
| metavar | Имя для аргумента в сообщениях об использовании. |
| dest | Имя атрибута, добавляемого к объекту, возвращаемому parse_args(). |
| deprecated | Является ли использование аргумента устаревшим. |
Также библиотека argparse может создавать сложный интерфейс из подкоманд и отдельных опций для них, но более удобный для этого подход реализован в библиотеке click.
16.1.3 Click
Библиотека click составляет интерфейс пользователя через декорирование функции, которые выступают подкомандами. Пример показан на Listing 16.3 . Документация.
1import click
2output_option = click.option("-o","--output","output", required = True, type=str, help = "Output directory")
3@click.group()
def main():
pass
4@main.command()
5@click.option("-s","--server","server", required = True, type=str,help = "Server IP address with local IEDB databases. Should be nextflow secret")
@click.option("-u","--user","user", required = True, type=str,help = "Database user with SELECT privileges in the selected database. Should be nextflow secret")
@click.option("-p","--password","password", required = True, type=str,help = "Database user password. MUST be nextflow secret")
@click.option("-d","--database","database", required = True, type=click.Choice(['IEDB', 'CEDAR'], case_sensitive=True))
6@output_option
7def mysql(server, user, password, database, output):
pass
@main.command()
@output_option
8def vdjdb(output):
pass
9if __name__ == "__main__":
main()- 1
- Импортируем библиотеку. Устанавливается через pip
- 2
- Если нам для всех команд общая опция, то мы можем создать специальный декоратор с настройкой разлчиных проверок
- 3
- Декорируем главную функцию как группу команд. Если нам не нужно. чтобы функция выполняла кроме подкоманд, то можно оставить её пустой
- 4
- Декорируем функцию, которая реализует логику подкоманды из группы команд main.
- 5
- Дополнительные декораторы нам объявляют параметры для этой подкоманды.
- 6
- Добавляем общую опцию
- 7
- Сигнатура функции должна включать в себя те параметры, которыми мы её декорировали
- 8
- Другая подкоманда
- 9
- Точка входа в программу.
16.2 Исключительные ситуации
- Исключение
-
событие, возникающее во время выполнения программы, которое нарушает ее нормальный ход
- Обработка исключительных ситуаций (англ. exception handling)
-
механизм языков программирования, предназначенный для описания реакции программы на ошибки времени выполнения и другие возможные проблемы (исключения), которые могут возникнуть при выполнении программы и приводят к невозможности (бессмысленности) дальнейшей отработки программой её базового алгоритма.
Исключительная ситуация - объект, который имеет свой собственный класс и иерархию наследования. В Python базовый класс для всех исключением является класс BaseException. Все остальные являются его потомками. Исключительная ситуация возникающая в сложной кодовой системе, как правило, ведет за собой целую цепь исключительных ситуаций, формируя стек исключений. Чтобы найти ошибку в своей кодовой базе, необходимо сделать обратный проход (traceback) от места начального сбоя до места, которое спровоцировало сбой и, опираясь на информационное сообщение (message), устранить причину. Таким образом, объект, наследуемый от BaseException, хранит это сообщение и стек исключений.
Перехват исключений осуществляется с помощью конструкции try-except-else-finally, как показано на Listing 16.4.
1import traceback
2try:
action_with_exception1()
action_with_exception2()
3except ZeroDivisionError as zde:
print(zde.message)
4except LookupError as le:
traceback.print_exc()
5except (OSError, TypeError) as te:
print("Meow")
6else:
print("SUCCESS")
7finally:
resource.close()- 1
- Полезный модуль для красивой выдачи отладочной информации
- 2
-
В блок
tryпомещаем код, в котором ожидаем ошибку - 3
-
Обработка ошибок осуществляется в блоке
except. Управление передается блоку в соответствии с отлавливаемой ошибкой. Объект исключения обладает полем сообщения об ошибке - 4
- Красивый вывод стека ошибок. Если надо отлавливать исключения, происходящие от общего предка, лучше указывать его, чем несколько потомков. Но указывать в качестве отлавливаемого исключения наиболее общий класс зачастую плохо!
- 5
- Если нужно при разных исключениях выполнять одно и то же действие, можно поместить их в один блок в виде кортежа.
- 6
-
Блок
elseвыполняется, если код в блокеtryотработал без ошибок: отлавливаемых и неотлавливаемых нами. - 7
-
Блок
finallyвыполняется в ЛЮБОМ случае. Полезен для освобождения ресурсов. Будьте осторожны, помещая в него директивыreturn. Будет возвращаться именно значение из блокаfinally, прочие будут игнорироваться.
Необязательно наличие всех блоков, Блоки except, else или finally могут отсутствовать.
Генерация исключений продемонстрирована в Listing 16.5
1if some_bad_condition:
raise SomeException("Message about situation!")- 1
-
Собственные исключения генерируются с помощью оператора
raise. Как правило, его имеет смысл использовать с каким-либо условием. В операторraiseпередаем экземпляр класса исключения. Конструктор класса может принимать строку с сообщением об ошибке.
Для больших задач бывает нужно определить собственный класс исключений. Во-первых, это повышает читаемость кода. Во-вторых, собственный класс может хранить дополнительную информацию об ошибке и реализовывать дополнительное поведение. Чтобы создать свой класс исключений, необходимо определить класс наследником от BaseException или его наследников, как продемонстрировано на Listing 16.6.
1class CustomException(BaseException):
2 def __init__(self, *args):
if args:
self.message = args[0]
else:
self.message = None
3 def __str__(self):
print('calling str')
if self.message:
return 'CustomException, {0} '.format(self.message)
else:
return 'CustomException has been raised'- 1
- Обязательно нужно унаследоваться!
- 2
- Конструктор, где можно реализовать свою логику создания и хранения информации
- 3
- Преобразования объекта в строку для получения сообщения об ошибке
16.3 Вопросы
- Понятия интерфейса. Разница интерфейса командной строки и графического интерфейса
- Модуль sys и его роль в формирвоании интерфейса командной строки
- Библиотека argparse
- Библиотека click и её отличия от argparse
- Понятие исключительной ситуации. Определение собственных исключений, обработка исключений, выбрасывание исключений.
16.4 Задания
- Rosalind - решение задач с написанием программ с интерфейсом командной строки
- Создайте индивидуально интерфейс командной строки для программы из вашего группового проекта с подкомандами, используя обе библиотеки.
