Перевод статьи How to Build Command Line Interfaces in Python With argparse.

Одной из сильных сторон языка Python является то, что он поставляется с обширной и достаточно универсальной стандартной библиотекой. Это делает его по настоящему отличным языком программирования для написания сценариев, в том числе с использованием интерфейса командной строки. И если вам необходимо написать сценарий, использующий командную строку для ввода дополнительных данных или управления его работой, то модуль argparse из состава стандартной библиотеки, поможет вам без установки дополнительных библиотек реализовать как простые, так и достаточно сложные интерфейсы.

Из этой статьи вы узнаете:

  • Что представляет из себя модуль argparse и о его возможностях.
  • Как использовать argparse для быстрого создания простого интерфейса командной строки.
  • Более сложные способы использования модуля argparse.

Эта статья написана для начинающих питонистов среднего уровня, которые, вероятно, пишут на Python простые скрипты для автоматизации рутинной работы, но никогда не реализовывали интерфейс командной строки для своих сценариев. Если вы вместо использования более надежного инструмента при разработке CLI привыкли устанавливать значения необходимых переменных в начале кода своих скриптов или вручную парсить содержимое списка аргументов командной строки sys.argv, то эта статья для вас.

Содержание

Как реализовать интерфейс командной строки?

Интерфейс командной строки (также известный под аббревиатурой CLI Command line interface) – это прежде всего способ взаимодействия с исполняемым сценарием. Python поставляется с несколькими модулями, которые позволяют реализовать интерфейс командной строки для ваших сценариев, тем не менее в настоящее время стандартным способом работы с интерфейсом командной строки в сценариях Python является модульargparse.

Библиотека argparse была выпущена 20 февраля 2011 года как часть стандартной библиотеки, начиная с версии Python 3.2. Она была представлена в Python Enhancement Proposal 389 и теперь предоставляет стандартный способ реализации CLI в сценариях Python как в версии 2.7, так и 3.2.

Этот новый модуль был выпущен в качестве замены старых getopt и optparse, поскольку в них отсутствовали некоторые важные функции.

Модуль argparse позволяет:

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

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

$ ls
dcdb_20180201.sg4    mastro35.sg4        openings.sg4
dcdb_20180201.si4    mastro35.si4        openings.si4
dcdb_20180201.sn4    mastro35.sn4        openings.sn4

И так мы видим, что в текущем каталоге есть куча файлов, но команда кроме их списка не вывела больше никакой информации о файлах.

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

Попробуйте снова выполнить команду ls, но теперь при ее вызове в командной строке добавьте опцию -l, как в примере ниже:

$ ls -l
total 641824
-rw-------  1 dave  staff  204558286  5 Mar  2018 dcdb_20180201.sg4
-rw-------  1 dave  staff  110588409  5 Mar  2018 dcdb_20180201.si4
-rw-------  1 dave  staff    2937516  5 Mar  2018 dcdb_20180201.sn4
-rw-------  1 dave  staff     550127 27 Mar  2018 mastro35.sg4
-rw-------  1 dave  staff      15974 11 Gen 17:01 mastro35.si4
-rw-------  1 dave  staff       3636 27 Mar  2018 mastro35.sn4
-rw-------  1 dave  staff      29128 17 Apr  2018 openings.sg4
-rw-------  1 dave  staff        276 17 Apr  2018 openings.si4
-rw-------  1 dave  staff         86 18 Apr  2018 openings.sn4

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

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

И так давайте сразу же проясним терминологию.

Аргумент – это отдельная часть командной строки, отделенная пробелами.

Опция – это особый тип аргумента (или его часть), который определяет поведение, введенной в командной строке, команды.

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

Рассмотрим следующую команду:

$ ls -l -s -k /var/log

В этом примере кроме команды ls в командной строке ей передаются пять различных аргументов:

  1. ls: имя выполняемой команды;
  2. -l: включается подробный формат вывода информации о файлах в списке;
  3. -s: выводит размер каждого файла из списка;
  4. -k: выводит размер файла в килобайтах.

/var/log: параметр, который предоставляет для выполняемой команды дополнительную информацию (путь к директории, для которой необходимо получить список, содержащихся в ней файлов).

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

$ ls -lsk /var/log

Теперь у нас всего три аргумента:

  1. ls: имя команды, которую мы выполняем;
  2. -lsk: три опции, которые вы хотите включить (комбинация -l, -s и -k);
  3. /var/log: параметр, который предоставляет для выполняемой команды дополнительную информацию (путь к директории, для которой необходимо получить список, содержащихся в ней файлов).

Когда используется интерфейс командной строки

Теперь, когда мы знаем, что представляет собой командная строка, нам наверняка станет интересно, когда лучше использовать ее в своих программах. Одно из известных, и в большей степени эмпирических правил, разработки прикладного программного обеспечения гласит, что если вы хотите обеспечить удобный способ настройки работы вашей программы, то в первую очередь следует рассмотреть возможность использования интерфейса командной строки. А стандартным способом сделать это на языке Python использовать модуль argparse.

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

Как использовать модуль argparse для разработки интерфейса командной строки

Использование модуля argparse подразумевает выполнение следующих четырех шагов:

  1. Импортировать модуль argparse;
  2. Создать объект парсера parser;
  3. Добавить в парсер необязательные и позиционные аргументы, которые будем в последствии обрабатывать в скрипте;
  4. Выполнить метод parse_args().

После выполнения метода parse_args() вы получите объект Namespace, содержащий свойства, имя и значение, которого соответствуют наименованию и значению соответствующего входного аргумента, полученного из командной строки.

Далее мы подробно рассмотрим эти четыре шага на практическом примере. Представим, что мы написали скрипт myls.py, который создает список файлов, содержащихся в текущем каталоге. Ниже представлен пример кода с возможной реализацией интерфейса командной строки для решения этой задачи, но пока без использования модуля argparse:

# myls.py
import os
import sys

if len(sys.argv) > 2:
    print('Вы передали в сценарий слишком много аргументов')
    sys.exit()

if len(sys.argv) 
                

Если вы попытаетесь выполнить свой скрипт, то увидите как он работает:

$ python myls.py
Вы должны определить путь до директории

$ python myls.py /mnt /proc /dev
Вы передали в сценарий слишком много аргументов

$ python myls.py /mnt
dir1
dir2

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

Теперь разберемся, как используя модуль argparse мы можем усовершенствовать наш скрипт:

# myls.py
# импортируем модуль argparse
import argparse

import os
import sys

# Создаем новый объект парсера
my_parser = argparse.ArgumentParser(description='Вывод списка содержимого директории')

# Добавляем аргументы, передаваемые для обработки
my_parser.add_argument('Path',
                       metavar='path',
                       type=str,
                       help='the path to list')

# Запускаем метод parse_args()
args = my_parser.parse_args()

input_path = args.Path

if not os.path.isdir(input_path):
    print('Определенный вами путь не существует')
    sys.exit()

print('\n'.join(os.listdir(input_path)))

Как видим код нашего скрипта с использованием модуля argparse очень изменился.

И первым его существенным отличием от предыдущей версии является то, что исчезли блоки кода операторов if, так как теперь проверку аргументов, предоставляемых пользователем, будет за нас делать модуль argparse.

В примере выше мы импортировали модуль argparse, а затем создали объект простого парсера (синтаксического анализатора) с кратким описанием цели программы description. Затем добавили один позиционный аргумент, который мы хотим получать от пользователя и далее обрабатывать далее его значение в программа. И наконец, мы выполнили метод parse_args(), который осуществляет парсинг (синтаксический анализ) входных аргументов, поступающих при запуске нашего скрипта. Как результат его работы мы получим объект Namespace, содержащий данные введенные пользователем.

Теперь, если мы запустим этот код, то увидим, что у нас получен совсем другой результат:

$ python myls.py
usage: myls.py [-h] path
myls.py: error: the following arguments are required: path

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

Вы можете заметить, что теперь наш скрипт принимает необязательную опцию (флаг) -h, как в примере ниже:

$ python myls.py -h
usage: myls.py [-h] path

List the content of a folder

positional arguments:
path        the path to list

optional arguments:
-h, --help  show this help message and exit

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

И наконец, всего по сути четырьмя строками кода мы передаем в переменную args результирующий объект типа Namespace, у которого каждое свойство представляет собой аргумент и его значение, полученные из командной строки. Это очень удобно.

Более сложные приемы использования модуля argparse

В предыдущем разделе мы узнали об основах использования модуля argparse и теперь для своих приложений можем реализовать простые интерфейсы командной строки. Однако с помощью этого модуля вы можете сделать гораздо больше. Далее вы узнаете почти все о его более продвинутых возможностях.

Устанавливаем название программы

По умолчанию модуль использует значение первого элемента массива sys.argv[0] для установки имени вашего приложения, которое, как вы вероятно уже знаете, является именем скрипта на языке Python, который выполняете. Однако вы можете определить любое имя для своей программы, используя параметр prog при создании объекта парсера:

# Create the parser
my_parser = argparse.ArgumentParser(prog='myls',
                                    description='List the content of a folder')

С помощью этого параметра вы указываете имя программы, которое будет использоваться в тексте справки:

$ python myls.py
usage: myls [-h] path
myls.py: error: the following arguments are required: path

Как видим, теперь в консоли выводится имя программы myls, а не myls.py как ранее.

Выводим справку по использованию вашего приложения

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

# Create the parser
my_parser = argparse.ArgumentParser(prog='myls',
                                    usage='%(prog)s [options] path',
                                    description='List the content of a folder')

Обратите внимание, что во время выполнения шаблон %(prog)s автоматически заменяется именем вашей программы:

$ python myls.py
usage: myls [options] path
myls: error: too few arguments

Добавляем свой текст в справку об аргументах, передаваемых в скрипт

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

  • description: для того, чтобы добавить текст, который будет отображается перед текстом справки;
  • epilog: для текста, отображаемого после текста справки.

Вы уже знакомы с параметром description из предыдущей главы, поэтому давайте посмотрим, как работает параметр epilog:

# Create the parser
my_parser = argparse.ArgumentParser(description='List the content of a folder',
                                    epilog='Enjoy the program! :)')

Таким образом параметр epilog позволяет добавить текст, который будет отображаться после стандартного текста справки:

$ python myls.py -h
usage: myls.py [-h] path

List the content of a folder

positional arguments:
path        the path to list

optional arguments:
-h, --help  show this help message and exit

Enjoy the program! :)

Как видим теперь в консоли отображается дополнительный текст, который мы передали в параметр epilog.

Добавляем возможность обработки символов префикса для файлов с командами

Когда вы имеете дело с очень длинными или сложными последовательностями аргументов, хорошей идеей будет сохранить все аргументы с передаваемыми им значениями, во внешнем файле, а затем указать вашему скрипту из файла загрузить все предустановленные настройки. Модуль argparse может сделать эту работу за вас прямо «из коробки».

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

# fromfile_example.py
import argparse

my_parser = argparse.ArgumentParser(fromfile_prefix_chars='@')

my_parser.add_argument('a',
                       help='a first argument')

my_parser.add_argument('b',
                       help='a second argument')

my_parser.add_argument('c',
                       help='a third argument')

my_parser.add_argument('d',
                       help='a fourth argument')

my_parser.add_argument('e',
                       help='a fifth argument')

my_parser.add_argument('-v',
                       '--verbose',
                       action='store_true',
                       help='an optional argument')

# Execute parse_args()
args = my_parser.parse_args()

print('If you read this line it means that you have provided '
      'all the parameters')

Обратите внимание, что мы использовали параметр fromfile_prefix_chars при создании объекта парсера.

И теперь, если вы попытаетесь выполнить свой скрипт не передав ему ни одного аргумента, вы получите следующее сообщение об ошибке:

$ python fromfile_example.py
usage: fromfile_example.py [-h] [-v] a b c d e
fromfile_example.py: error: the following arguments are required: a, b, c, d, e

Здесь вы можете видеть, что модуль argparse «жалуется», так как вы не предоставили достаточно аргументов.

Итак, давайте создадим файл с именем args.txt, который содержит все необходимые настройки (значения передаваемые аргументам) по строкам, порядок которых соответствует добавляемым в объект парсера аргументам, например:

first
second
third
fourth
fifth

Теперь, когда мы задали символ префикса @ для получения значений аргументов из внешнего файла, откройте терминал и попробуйте выполнить наш скрипт следующим образом:

$ python fromfile_example.py @args.txt
If you read this line it means that you have provided all the parameters

Отлично, argparse прочитал значения аргументов из файла args.txt.

Разрешаем или запрещаем сокращать имена аргументов

Еще одна из функций, которую предоставляет модуль argparse «из коробки» – это возможность обрабатывать сокращенные имена аргументов. Рассмотрим следующий скрипт, который выводит в консоли некоторое значение, передаваемое вами в командную строку для аргумента --input:

# abbrev_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('--input', action='store', type=int, required=True)
my_parser.add_argument('--id', action='store', type=int)

args = my_parser.parse_args()

print(args.input)

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

Теперь давайте посмотрим, как модуль argparse обрабатывает сокращенные наименования аргументов. Для этого вызовем наш скрипт несколько раз, указывая при каждом запуске разные сокращённые имена нашего единственного аргумента:

$ python abbrev_example.py --input 42
42

$ python abbrev_example.py --inpu 42
42

$ python abbrev_example.py --inp 42
42

$ python abbrev_example.py --in 42
42

При вводе через консоль имен необязательных параметров всегда можно сокращать их имя, если только это сокращение не приведет к их неправильной интерпретации. Но что произойдет, если вы попытаетесь выполнить скрипт, указав в консоли --i 42? В этом случае, argparse не сможет определить однозначно, хотите ли вы передать значение 42 в аргумент --input или же в --id, поэтому в консоли при выполнении скрипта будет отображено соответствующее сообщение об ошибке интерпретации данных:

$ python abbrev_example.py --i 42
usage: abbrev_example.py [-h] --input INPUT [--id ID]
abbrev_example.py: error: ambiguous option: --i could match --input, --id

Если вам не нравится такое поведение, и вы хотите заставить своих пользователей указывать полное имя, используемых параметров, то можете отключить эту возможность, задав соответствующее значение False для параметра allow_abbrev при создании нового объекта парсера:

# abbrev_example.py
import argparse

my_parser = argparse.ArgumentParser(allow_abbrev=False)
my_parser.add_argument('--input', action='store', type=int, required=True)

args = my_parser.parse_args()

print(args.input)

Теперь, если вы попробуете запустить этот код, то увидите, что сокращения больше не разрешены для ввода:

$ python abbrev_example.py --inp 42
usage: abbrev_example.py [-h] --input INPUT
abbrev_example.py: error: the following arguments are required: --input

Соответствующее сообщение об ошибке информирует пользователя, что значение параметра --input не было указано, потому что сокращение --inp не было корректно распознано.

Использование автоматической справки

В предыдущих примерах мы использовали флаг -h для получения информации справки о командах распознаваемых вашим приложением. Это очень удобная возможность, которую модуль argparse позволяет использовать без необходимости писать код. Однако иногда вам может потребоваться отключить эту функцию. Для этого просто используйте параметр add_help при создании нового объекта парсера:

# Create the parser
my_parser = argparse.ArgumentParser(description='List the content of a folder',
                                    add_help=False)

В коде выше мы передаем значение False параметру add_help, поэтому теперь, если вы запустите код, то увидите, что флаг -h больше не принимается:

$ myls.py
usage: myls.py path
myls.py: error: the following arguments are required: path

Как видите, -h флаг больше не отображается и не принимается для обработки и вывода информации справки.

Установка имени или флагов аргументов

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

  1. Позиционные аргументы
  2. Необязательные аргументы

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

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

Например, рассмотрим команду cp, использующуюся в Linux (или команду copy в Windows). Ниже приведен шаблон их стандартного применения:

$ cp [OPTION]... [-T] SOURCE DEST

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

Необязательные аргументы по сути не являются обязательными, но когда они используются, они могут изменять поведение команды во время выполнения. В примере с командой cpнеобязательным аргументом является, например, флаг -r, который предписывает команде рекурсивно копировать каталоги.

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

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

Например, попробуем изменить наш скрипт myls.py следующим образом:

# myls.py
# Импортируем argparse
import argparse

import os
import sys

# Создаем новый объект парсера parser
my_parser = argparse.ArgumentParser(description='List the content of a folder')

# Добавляем аргументы, которые будут обрабатываться в скрипте
my_parser.add_argument('Path',
                       metavar='path',
                       type=str,
                       help='the path to list')
my_parser.add_argument('-l',
                       '--long',
                       action='store_true',
                       help='enable the long listing format')

# запускаем метод parse_args()
args = my_parser.parse_args()

input_path = args.Path

if not os.path.isdir(input_path):
    print('The path specified does not exist')
    sys.exit()

for line in os.listdir(input_path):
    if args.long:  # Если задана опция выводим подробную информацию о файле
        size = os.stat(os.path.join(input_path, line)).st_size
        line = '%10d  %s' % (size, line)
    print(line)

Теперь попробуйте выполнить его, чтобы увидеть, что новая опция -l теперь принимается скриптом для дальнейшей обработки:

$ python myls.py -h
usage: myls.py [-h] [-l] path

List the content of a folder

positional arguments:
path        the path to list

optional arguments:
-h, --help  show this help message and exit
-l, --long  enable the long listing format

Как видите, теперь скрипт принимает (но не требует ввода) опцию -l, которая позволяет пользователю выводить в консоли список содержимого каталога в более детализированном формате.

Определяем действия со значением аргумента

Когда мы, в примере выше, добавляли необязательный аргумент в интерфейс командной строки, мы могли определить, какое действие выполнять при его указании пользователем. Тем не менее в начале необходимо указать, как сохранять значение в результирующем объекте Namespace, который вы получите при вызове метода parse_args().

Есть несколько типов действий, которые предопределены и готовы к использованию. Разберем их подробно:

  • store сохраняет как есть входное значение в объект Namespace. (Это действие по умолчанию).
  • store_const сохраняет значение как константу, если указаны соответствующие необязательные аргументы (Этот тип действий удобно задействовать для передачи флагов опций).
  • store_true сохраняет значение как логическое True, если указан соответствующий необязательный аргумент, в противном случае сохраняет его значение как False.
  • store_false сохраняет значение как логическое False, если указан соответствующий необязательный аргумент, в противном случае сохраняет его значение как True.
  • append сохраняет переданное значение в список, добавляя в него значение каждый раз, когда пользователем предоставляется новое значение некоторой опции.
  • append_const сохраняет переданное значение в список как константу, добавляя в него значение каждый раз, когда пользователем предоставляется значение некоторой опции.
  • count сохраняет значение с типом int (целое число), соответствующее числу переданных пользователем значений для определенной опции.
  • help выводит в консоли текст справки и заканчивает выполнение скрипта.
  • version выводит в консоли версию вашей программы и заканчивает выполнение скрипта.

Давайте рассмотрим пример кода для демонстрации всех этих действий:

# actions_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.version = '1.0'
my_parser.add_argument('-a', action='store')
my_parser.add_argument('-b', action='store_const', const=42)
my_parser.add_argument('-c', action='store_true')
my_parser.add_argument('-d', action='store_false')
my_parser.add_argument('-e', action='append')
my_parser.add_argument('-f', action='append_const', const=42)
my_parser.add_argument('-g', action='count')
my_parser.add_argument('-i', action='help')
my_parser.add_argument('-j', action='version')

args = my_parser.parse_args()

print(vars(args))

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

$ python actions_example.py
{'a': None, 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': None}

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

Аргумент, переданный с использованием типа действия store сохраняет значение, без какого-либо преобразования:

$ python actions_example.py -a 42
{'a': '42', 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': None}

$ python actions_example.py -a "test"
{'a': 'test', 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': None}

Тип действия store_const, предписывает сохранить передаваемое в качестве аргумента значение как константу. В нашем примере мы передали только аргумент b, и теперь свойство args.b содержит значение 42:

$ python actions_example.py -b
{'a': None, 'b': 42, 'c': False, 'd': True, 'e': None, 'f': None, 'g': None}

Действие с типом store_true сохраняет переданное логическое значение True в случае если аргумент передается, и сохраняет значение аргумента по умолчанию как False если оно не задается пользователем. Если вам нужно обратное поведение, используйте действие типа store_false:

$ python actions_example.py
{'a': None, 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': None}
$ python actions_example.py -c
{'a': None, 'b': None, 'c': True, 'd': True, 'e': None, 'f': None, 'g': None}
$ python actions_example.py -d
{'a': None, 'b': None, 'c': False, 'd': False, 'e': None, 'f': None, 'g': None}

Действие типа append позволяет создать список значений, переданных при вызове скрипта одному и тому же аргументу:

$ python actions_example.py -e me -e you -e us
{'a': None, 'b': None, 'c': False, 'd': True, 'e': ['me', 'you', 'us'], 'f': None, 'g': None}

Действие append_const и append схожи, но оно всегда добавляет только одинаковое постоянное значение:

$ python actions_example.py -f -f
{'a': None, 'b': None, 'c': False, 'd': True, 'e': None, 'f': [42, 42], 'g': None}

Действие count подсчитывает, сколько раз был передан определенный аргумент. Это очень полезно, если вы, например, хотите реализовать уровень детализации для своей программы, поскольку вы можете определить уровень, -v который имеет меньших уровень детализации, чем -vvv:

$ python actions_example.py -ggg
{'a': None, 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': 3}
$ python actions_example.py -ggggg
{'a': None, 'b': None, 'c': False, 'd': True, 'e': None, 'f': None, 'g': 5}

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

$ python actions_example.py -i
usage: actions_example.py [-h] [-a A] [-b] [-c] [-d] [-e E] [-f] [-g] [-i]
[-j]

optional arguments:
-h, --help  show this help message and exit
-a A
-b
-c
-d
-e E
-f
-g
-i
-j          show program's version number and exit

Действие version просто выводит версию нашей программы (определенную путем присвоения значения параметру version при создании объекта парсера), а затем завершает выполнение скрипта:

$ python actions_example.py -j
1.0

Еще одна полезная возможность предоставляемая модулем argparse – создавать полностью настраиваемые пользовательские действия. Это делается путем создания подкласса класса argparse.Action и дальнейшей реализации всего пары методов.

Рассмотрим следующий пример, который добавляет пользовательское действие, схожее с store, но чуть более «многословное», чем стандартное:

# custom_action.py
import argparse

class VerboseStore(argparse.Action):
    def __init__(self, option_strings, dest, nargs=None, **kwargs):
        if nargs is not None:
            raise ValueError('nargs not allowed')
        super(VerboseStore, self).__init__(option_strings, dest, **kwargs)

    def __call__(self, parser, namespace, values, option_string=None):
        print('Here I am, setting the ' \
              'values %r for the %r option...' % (values, option_string))
        setattr(namespace, self.dest, values)

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-i', '--input', action=VerboseStore, type=int)

args = my_parser.parse_args()

print(vars(args))

Попробуйте выполнить этот код, чтобы проверить, как оно работает:

$ python custom_action.py -i 42
Here I am, setting the values 42 for the '-i' option...
{'input': 42}

Как вы можете видеть, скрипт вывел в консоли строку сообщения об установке значения 42 для аргумента -i .

Задаем количества значений, которые могут быть переданы опции

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

Например, вам необходимо задать необязательный аргумент, который принимает три значения. Для этого вы указываете число 3 в качестве значения параметра nargs при создании нового объекта парсера:

# nargs_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('--input', action='store', type=int, nargs=3)

args = my_parser.parse_args()

print(args.input)

Теперь скрипт принимает три значения аргумента --input:

$ python nargs_example.py --input 42
usage: nargs_example.py [-h] [--input INPUT INPUT INPUT]
nargs_example.py: error: argument --input: expected 3 arguments

$ python nargs_example.py --input 42 42 42
[42, 42, 42]

Как видите, в переменной args.input находится список, содержащий три переданных значения.

Так же параметр nargs может принимать следующие предопределенные значения, предписывающие ему определенное поведение:

  • ?: параметр принимает одно значение, которое является необязательным;
  • *: параметр принимает любое количество значений, которые будут объединены в список;
  • +: аналогично *, но принимается хотя бы одно значение;
  • argparse.REMAINDER: принимает все значения, переданные в командной строке при запуске скрипта.

Так, в примере ниже позиционный аргумент input принимает одно значение, если он указан, но если значение для него не передано, то используется значение, указанное в параметре default:

# nargs_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('input',
                       action='store',
                       nargs='?',
                       default='my default value')

args = my_parser.parse_args()

print(args.input)

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

$ python nargs_example.py 'my custom value'
my custom value

$ python nargs_example.py
my default value

Чтобы получить любое необходимое количество значений и собрать их все в один список, вам необходимо передать значение * в параметр nargs следующим образом:

# nargs_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('input',
                       action='store',
                       nargs='*',
                       default='my default value')

args = my_parser.parse_args()

print(args.input)

Код из примера выше позволяет пользователю задавать любое количество значений для аргумента input:

$ python nargs_example.py me you us
['me', 'you', 'us']

$ python nargs_example.py
my default value

Если вам нужно передать в скрипт заранее неизвестное количество значений, и вы хотите быть уверены, что будет передано хоть одно значение, вы можете использовать предопределённое значение +, передав его в параметр nargs:

# nargs_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('input', action='store', nargs='+')

args = my_parser.parse_args()

print(args.input)

В этом случае, если вы выполните скрипт без передаваемых аргументов, то получите соответствующее сообщение об ошибке:

$ python nargs_example.py me you us
['me', 'you', 'us']

$ python nargs_example.py
usage: nargs_example.py [-h] input [input ...]
nargs_example.py: error: the following arguments are required: input

И наконец, если вам нужно обработать все оставшиеся аргументы, переданные в командную строку, и поместить их в список, то в параметр nargs необходимо передать значение argparse.REMAINDER:

# nargs_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('first', action='store')
my_parser.add_argument('others', action='store', nargs=argparse.REMAINDER)

args = my_parser.parse_args()

print('first = %r' % args.first)
print('others = %r' % args.others)

Теперь, если вы выполните этот скрипт, то увидите, что первое значение будет передано в первый аргумент, а все остальные передав – во второй:

$ python nargs_example.py me you us
first = 'me'
others = ['you', 'us']

Обратите внимание, что все оставшиеся значения будут помещены в список.

Устанавливаем для аргумента значение по умолчанию

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

Рассмотрим как можно переопределить для аргумента значение передающееся ему по умолчанию:

# default_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-a', action='store', default='42')

args = my_parser.parse_args()

print(vars(args))

Если вы выполните этот пример кода, не передав при запуске скрипта значение параметра -a, то получите следующий результат:

$ python default_example.py
{'a': '42'}

Теперь для параметра -a установлено значение по умолчанию 42, даже если вы явно не передаете его значение в командной строке.

Определяем тип значения передаваемого в аргумент

По умолчанию все значения аргументов, передаваемых в скрипт при запуске, обрабатываются так, как если бы они были строками. Однако вы можете определить тип любой значений для соответствующих свойств объекта Namespace, который получаете после вызова метода parse_args(). Для этого передайте нужный тип ожидаемого значения в параметр type, следующим образом:

# type_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-a', action='store', type=int)

args = my_parser.parse_args()

print(vars(args))

Указывая значение int для аргумента, вы предписывает модулю argparse, принимать в качестве значения свойства a объекта Namespace вместо строки целое число типа int:

$ python type_example.py -a 42
{'a': 42}

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

$ python type_example.py -a "that's a string"
usage: type_example.py [-h] [-a A]
type_example.py: error: argument -a: invalid int value: "that's a string"

Как видим в нашем случае сообщение об ошибке предельно конкретно информирует о том, что вы должны передать в аргумент целое число int вместо строки.

Определяем список допустимых значений для определённого аргумента

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

# choices_ex.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-a', action='store', choices=['head', 'tail'])

args = my_parser.parse_args()

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

# choices_ex.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-a', action='store', type=int, choices=range(1, 5))

args = my_parser.parse_args()

print(vars(args))

Для случая выше значения, передаваемое через командную строку, будет автоматически проверяться на соответствие заданному диапазону:

$ python choices_ex.py -a 4
{'a': 4}

$ python choices_ex.py -a 40
usage: choices_ex.py [-h] [-a {1,2,3,4}]
choices_ex.py: error: argument -a: invalid choice: 40 (choose from 1, 2, 3, 4)

Если введенное число выходит за пределы определенного диапазона, то вы получите сообщение о ошибке.

Определяем обязательные аргументы

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

# required_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-a',
                       action='store',
                       choices=['head', 'tail'],
                       required=True)

args = my_parser.parse_args()

print(vars(args))

Теперь если вы используете параметр required, в который передаете значение True, то пользователь будет вынужден при запуске скрипта обязательно передавать его значение:

$ python required_example.py
usage: required_example.py [-h] -a {head,tail}
required_example.py: error: the following arguments are required: -a

$ python required_example.py -a head
{'a': 'head'}

Краткое описание того, что делает аргумент

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

И чтобы сделать свои скрипты еще удобнее в использовании, вы можете добавить текст более подробной справки по аргументам, передаваемым вашему скрипту. Таким образом пользователь может получить больше информации о работе вашего приложения, просто добавив флаг -h при его запуске:

# help_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-a',
                       action='store',
                       choices=['head', 'tail'],
                       help='set the user choice to head or tail')

args = my_parser.parse_args()

print(vars(args))

В этом примере показано, как определить текст справки для аргумента -a, и сделать его использование более понятным для пользователя:

$ python help_example.py -h
usage: help_example.py [-h] [-a {head,tail}]

optional arguments:
-h, --help      show this help message and exit
-a {head, tail}  set the user choice to head or tail

Определение текста справки для аргументов, используемых при работе скрипта, действительно хорошая идея.

Определение групп взаимоисключающих параметров

Другой интересный вариант, который предлагается к использованию модулем argparse – это возможность создать отдельную группу взаимоисключающую параметров, которые не могут одновременно передаваться в командной строке при запуске скрипта:

# groups.py
import argparse

my_parser = argparse.ArgumentParser()
my_group = my_parser.add_mutually_exclusive_group(required=True)

my_group.add_argument('-v', '--verbose', action='store_true')
my_group.add_argument('-s', '--silent', action='store_true')

args = my_parser.parse_args()

print(vars(args))

В примере выше мы определили флаги -v и -s, которые не могут одновременно использоваться при вводе в командной строке. Соответствующее сообщение справки, предоставляемой модулем argparse по умолчанию, отражает это ограничение:

$ python groups.py -h
usage: groups.py [-h] (-v | -s)

optional arguments:
  -h, --help     show this help message and exit
  -v, --verbose
  -s, --silent

$ python groups.py -v -s
usage: groups.py [-h] (-v | -s)
groups.py: error: argument -s/--silent: not allowed with argument -v/--verbose

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

Определяем наименование аргумента в сообщениях справки об его использовании

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

# metavar_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-v',
                       '--verbosity',
                       action='store',
                       type=int,
                       metavar='LEVEL')

args = my_parser.parse_args()

print(vars(args))

Теперь, если вы запустите этот скрипт с флагом -h, то в тексте справки значение флага -v будет сопоставлено с его новым наименованием LEVEL:

$ python metavar_example.py -h
usage: metavar_example.py [-h] [-v LEVEL]

optional arguments:
  -h, --help            show this help message and exit
  -v LEVEL, --verbosity LEVEL

Определяем имя атрибута, который будет добавлен к объекту Namespace после обработки

Как мы уже знаем, что если добавляется новый аргумент в создаваемый объект парсера, то его значение в после обработки сохраняется в свойстве с соответствующем именем в объекте Namespace. Это свойство по умолчанию будет иметь имя, которое мы передали в качестве первого параметра в метод add_argument(), что справедливо, как для определения позиционного аргумента, так и для необязательных опций. Если в строке, передаваемой в параметр используются дефисы (что бывает довольно часто), то в имени свойства они будут преобразованы в символы подчеркивания:

import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-v',
                       '--verbosity-level',
                       action='store',
                       type=int)

args = my_parser.parse_args()
print(args.verbosity_level)

Однако есть возможность непосредственно указать имя свойства для результирующего объекта Namespace, содержащего значение передаваемое аргументу. Для этого необходимо указать его имя в параметре dest у метода добавляющего новый аргумент в объект парсера:

# dest_example.py
import argparse

my_parser = argparse.ArgumentParser()
my_parser.add_argument('-v',
                       '--verbosity',
                       action='store',
                       type=int,
                       dest='my_verbosity_level')

args = my_parser.parse_args()

print(vars(args))

Запустив этот скрипт на выполнение, в результате увидим, что теперь переменная args содержит свойство my_verbosity_level, хотя по умолчанию его наименование должно было быть verbosity:

$ python dest_example.py -v 42
{'my_verbosity_level': 42}

Так как в примере выше мы использовали параметр dest, в котором указали для нее другое имя, то в результате получили свойство my_verbosity_level с соответствующим значением, переданным через интерфейс командной строки.

Заключение

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

Из этой статьи вы так же узнали:

  • о модуле argparse, его возможностях и преимуществах для написания сценариев на Python, использующих CLI интерфейс командной строки;
  • как использовать модуль argparse для быстрого создания простейшего интерфейса командной строки;
  • расширенные возможности использования модуля argparse.

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

Оставить комментарий