Пользователь
0,0
рейтинг
24 мая 2012 в 06:58

Разработка → Argparse — парсим аргументы и параметры командной строки с легкостью из песочницы

Начиная с версии Python 2.7, в набор стандартных библиотек была включена библиотека argparse для обработки аргументов (параметров, ключей) командной строки. Хотелось бы остановить на ней Ваше внимание.

Для начала рассмотрим, что интересного предлагает argparse.

Argparse — это изящный инструмент для:
  • анализа аргументов sys.argv;
  • конвертирования строковых аргументов в объекты Вашей программы и работа с ними;
  • форматирования и вывода информативных подсказок;
  • многого другого.


Одним из аргументов противников включения argparse в Python был довод о том, что в стандартных модулях и без этого содержится две библиотеки для семантической обработки (парсинга) параметров командной строки. Однако, как заявляют разработчики argparse, библиотеки getopt и optparse уступают argparse по нескольким причинам:
  • обладая всей полнотой действий с обычными параметрами командной строки, они не умеют обрабатывать позиционные аргументы (positional arguments). Позиционные аргументы — это аргументы, влияющие на работу программы, в зависимости от порядка, в котором они в эту программу передаются. Простейший пример — программа cp, имеющая минимум 2 таких аргумента («cp source destination»).
  • argparse дает на выходе более качественные сообщения о подсказке при минимуме затрат (в этом плане при работе с optparse часто можно наблюдать некоторую избыточность кода);
  • argparse дает возможность программисту устанавливать для себя, какие символы являются параметрами, а какие нет. В отличие от него, optparse считает опции с синтаксисом наподобии "-pf, -file, +rgb, /f и т.п. «внутренне противоречивыми» и «не поддерживается optpars'ом и никогда не будет»;
  • argparse даст Вам возможность использовать несколько значений переменных у одного аргумента командной строки (nargs);
  • argparse поддерживает субкоманды (subcommands). Это когда основной парсер отсылает к другому (субпарсеру), в зависимости от аргументов на входе.


Для начала работы с argparse необходимо задать парсер:

ap.py:
import argparse
parser = argparse.ArgumentParser(description='Great Description To Be Here')


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

parser.add_argument('-n', action='store', dest='n', help='Simple value')


Если действие (action) для данного аргумента не задано, то по умолчанию он будет сохраняться (store) в namespace, причем мы также можем указать тип этого аргумента (int, boolean и тд). Если имя возвращаемого аргумента (dest) задано, его значение будет сохранено в соответствующем атрибуте namespace.

В нашем случае:
print parser.parse_args(['-n', '3'])

Namespace(n='3')

print parser.parse_args([])

Namespace(n=None)

print parser.parse_args(['-a', '3'])
error: unrecognized arguments: -a 3


Простой пример программы, возводящей в квадрат значение позиционного аргумента (square) и формирующей вывод в зависимости от аргумента опционального (-v):


import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display a square of a given number")
parser.add_argument("-v", "--verbose", action="store_true",
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbose:
    print("the square of {} equals {}".format(args.square, answer))
else:
    print(answer)


Остановимся на действиях (actions). Они могут быть следующими:
— store: возвращает в пространство имен значение (после необязательного приведения типа). Как уже говорилось, store — действие по умолчанию;

— store_const: в основном используется для флагов. Либо вернет Вам значение, указанное в const, либо (если ничего не указано), None. Пример:

parser.add_argument('--LifetheUniverseandEverything', action='store_const', const=42)
print parser.parse_args(['--LifetheUniverseandEverything'])

Namespace(LifetheUniverseandEverything=42)


— store_true / store_false: аналог store_const, но для булевых True и False;

— append: возвращает список путем добавления в него значений агрументов. Пример:
parser.add_argument('--l', action='append')
print parser.parse_args('--l a --l b --l Y'.split())

Namespace(l=['abY'])


— append_const: возвращение значения, определенного в спецификации аргумента, в список. Пока у меня не было кейса, в котором понадобился бы append_const.

— count: как следует из названия, считает, сколько раз встречается значение данного аргумента. Пример:

parser.add_argument('--verbose', '-v', action='count')
print parser.parse_args('-vvv'.split()) 

Namespace(verbose=3)


В зависимости от переданного в конструктор парсера аргумента add_help (булевого типа), будет определяться, включать или не включать в стандартный вывод по ключам ['-h', '--help'] сообщения о помощи. То же самое будет иместь место с аргументом version (строкового типа), ключи по умолчанию: ['-v', '--version']. При запросе помощи или номера версии, дальнейшее выполнение прерывается.

parser = argparse.ArgumentParser(add_help=True, version='4.0')


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

parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument('--user', action="store")
parent_parser.add_argument('--password', action="store")
child_parser = argparse.ArgumentParser(parents=[parent_parser])
child_parser.add_argument('--show_all', action="store_true")
print child_parser.parse_args(['--user', 'guest'])

Namespace(password=None, show_all=False, user='guest')


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

parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument('--user', action="store")
parent_parser.add_argument('--password', action="store")
child_parser = argparse.ArgumentParser(parents=[parent_parser], conflict_handler='resolve')
child_parser.add_argument('--user', action="store", default="Guest")
print child_parser.parse_args()

Namespace(password=None, user='Guest')


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

ap.py
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='List of commands')
# A list command
list_parser = subparsers.add_parser('list', help='List contents')
list_parser.add_argument('dirname', action='store', help='Directory to list')
# A create command
create_parser = subparsers.add_parser('create', help='Create a directory')
create_parser.add_argument('dirname', action='store', help='New directory to create')
create_parser.add_argument('--read-only', default=False, action='store_true',
                           help='Set permissions to prevent writing to the directory',
                           )


Вот что выдаст программа с ключом '-h':

usage: ap.py [-h] {list,create} ...

positional arguments:
{list,create} list of commands
list List contents
create Create a directory

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


В примере можно отметить следующие вещи:
1. позиционные аргументы list, create, передаваемые программе — по сути субпарсеры;
2. аргумент '--read-only' субпарсера create_parser — опциональный, dir_name — необходимый для обоих субпарсеров;
3. предусмотрена справка (помощь) как для парсера, так и для каждго из субпарсеров.

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

Список литературы:
Why aren't getopt and optparse enough (PEP 389)
Argparse positional and optional arguments
Argparse documentation
Parser for command-line options, arguments and sub-commands
Евгений @w1cked
карма
11,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (10)

  • 0
    Есть ещё отличный фрэймворк для создания cli приложений cliff.
  • 0
    Спасибо! Никак не доходили руки разобраться с библиотекой поглубже, а тут ваша статья — однозначно в избранное.
  • +1
    Пожалуйста, сделайте питоновскую подсветку кода. Спасибо.
  • 0
    Спасибо, но мне кажется Вы зря потратили на эту простую библиотеку. Есть куда более сложная и достаточно Важная это logging. Касательно же этой библиотеки Вы зря не упомянули 'metavar' это очень полезная вещь!
    • 0
      Это первая часть, в продолжении постараюсь упомянуть.
  • 0
    Отличная библиотек, жаль в C++ нету стандартной библиотеки для работы с командной строкой
    • 0
      В C++ из коробки вообще практически ничего нет, хоть что-то появляется если использовать STL и Boost.
      Взять Qt, столько готовых компонент сразу, даже если про гуй не говорить.

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

      Перл тоже ничего, но надо лезть на сипан если что-то потребовалось…

      Сорри за халивар, понятное дело что для C++ существует миллион библиотек, которые решают любые задачи, но мне кажется Яву выбирают часто не только потому что там не надо заботиться об освобождении памяти, а потому что кто-то уже сделал за вас выбор базовых библиотек и вы просто используете и есть надежда что всё это стыкуется с другими компанентами (ведь кто-то в целом за всё это отвечает).
      • 0
        > Взять Qt, столько готовых компонент сразу, даже если про гуй не говорить.

        Но парсера командной строки в Qt тоже нет :)
  • 0
    Ещё полезно было бы упомянуть чем она отличается от optparse и как перевезти проекты в которых уже используется optparse, ибо судя по документация последняя уже объявлена как деприкейтыд.
  • 0
    Как вовремя нашел статью. Как раз копаюсь с argparse, optparse. Жду продолжения.

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