Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
137 lines (100 sloc) 7.87 KB

Python. Подкоманды и argparse

Поговорим об улучшении использования argparse и подкоманд в повседневной жизни.

В моей практике почти в каждом проекте есть интерфейс для командной строки, это может быть manage.py в веб проекте, просто скрипт бекапа или даже приложение GTK. В python 2.7 и 3.2 появился очень мощный модуль argparse для обработки параметров командной строки, и в нем есть "из коробки" поддержка подкоманд и это очень круто. Но есть в этом модуле маленький недостаток - интерфейс его использования немного избыточен.

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

Вроде не так уж все и плохо, обычный интерфейс. Есть дублирование параметра --settings, но чтоб он был привязан к каждой подкоманде его нельзя вешать на базовый парсер. Также нам пришлось переносить строки для соблюдения PEP 8, при том что не помещались считанные символы. Можно укоротить переменные cmd_run, cmd_test до run, test или даже до r, t, но суть не в этом. Эти переменные, в принципе, не нужны, если добавить цепочки вызовов:

cmds.add_parser('run').add_argument('port').set_defaults(func=run_server)

На чистом argparse цепочек вызовов не получится, хотя может в каких-то случаях использования они и не нужны. В моей практике чаще хочется цепочек.

В самом начале примера объявлена пара функций и есть проекты, которые превращают эти функции в подкоманды, типа: opster, argh, komandr. Последние два основаны на argparse, а opster использует getopt.

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

Вывод главного help такой же как из первого примера:

usage: example-argh.py [-h] {run,test,t,te} ...

positional arguments:
  {run,test,t,te}
    run            run dev server
    test (t, te)   run tests

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

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

===pure argparse===
usage: app run [-h] [-s SETTINGS] [-P PORT] [-H HOST] [--no-reload]

optional arguments:
  -h, --help            show this help message and exit
  -s SETTINGS, --settings SETTINGS
                        application settings
  -P PORT, --port PORT  server port
  -H HOST, --host HOST  server host
  --no-reload           without reloading


===argh===
usage: example-argh.py run [-h] [--host HOST] [-p PORT] [-n] [-s SETTINGS]

run dev server

optional arguments:
  -h, --help            show this help message and exit
  --host HOST
  -p PORT, --port PORT
  -n, --no-reload
  -s SETTINGS, --settings SETTINGS

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

Вся прелесть argparse, что с python 2.7 и 3.2 он входит в стандартную библиотеку и реально крут по сравнению с тем же getopt и optparse. А перечисленные выше улучшаторы - это отдельные пакеты и таскать их зависимостями в каждый проект не прикольно, особенно если проект минималистичный или небольшой скрипт с подкомандами. Еще в улучшаторах часто присутствует немного магии, argparse же прямой как двери.

Хорошо бы использовать argparse, но как-то покрасивее, чем в первом примере.

Следующий пример - мой любимый способ:

По-моему, выходит очень читабельно:
  • это чистый argparse и если его интерфейс чаще использовать, то даже этот сложный интерфейс запомнится;
  • глобальные опции, типа --settings, мы можем определять в одном месте без дублирования;
  • чем больше подкоманд и параметров, тем оправданнее добавление вложенной функции cmd;
  • обратный слеш в цепочках вызовов мне больше нравится, хотя перенос строк больше люблю делать внутри скобок.

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

Вывод довольно банальный: у python очень крутая стандартная библиотека, argparse - очень мощный инструмент для работы с параметрами командной строки. И даже если есть какие-то библиотеки с красивыми плюшками (argh, opster или docopt) у них скорее всего тоже найдутся свои недостатки, поэтому мой выбор - подточить использование argparse и забыть про дополнительные зависимости.