Начиная с версии 2.7 стандартным модулем парсинга командной стоки является argparse.
Создание объекта парсера
import argparse
parser = argparse.ArgumentParser()
Вообще argparse решает две основные задачи а) парсинг аргументов вызова скрипта и б) вывод справки об этих аргументах.
Справка по аргументам
Выводимая в консоль справка состоит из 4-х блоков
- командная строка запуска скрипта (usage). По-умолчанию parse_args() анализирует все добавленные в наш объект аргументы и генерирует строку usage с учетом синтаксиса и типа этих арументов. Значение usage можно изменить при создании объекта парсера, определив параметр usage, например так
parser=argparse.ArgumentParser(usage='Script options "%(prog)s"')
- описание скрипта (description). По умолчанию не задано, задается при создании экземпляра парсера
parser=argparse.ArgumentParser(description='The "%(prog)s" is a test script')
- Далее идет перечисление аргументов, добавленных в созданный экземпляр парсера, и их описание. При этом, при выводе аргументы группируются - сначала выводится справка о позиционных аргументах, а затем - по опциональным
- Заключительный блок (epilog), например, с примерами использования ключей
parser=argparse.ArgumentParser(epilog='Sample: "%(prog)s -h" - view help')
Протестировать, как будет выводиться справочная информация, можно через метод экземпляра парсера print_help()
parser=argparse.ArgumentParser()
parser.print_help()
Argparser имеет несколько встроенных форматов, позволяющих выводить справочную информацию в различном представлении и с различной степенью детализации. Для этого используются следующие классы
- RawTextHelpFormatter (класс по умолчанию)
- ArgumentDefaultsHelpFormatter - добавляет информацию о значениях аргументов по умолчанию
- MetavarTypeHelpFormatter - выводит тип значения аргумента
Чтобы настроить нужный формат вывода справки, создаем экземпляр парсера так
parser=argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.print_help()
Добавление аргументов
По умолчанию, в экземпляр созданный ArgumentParser() сразу добавляется аргумент '-h' - вывод справочной информации о скрипте и аргументах его запуска. Отключить добавление ключа '-h' можно так
parser=ArgumentParser(add_help=False)
Остальные аргументы добавляются вызовом метода add_argument(). Аргументы бывают позиционные (обязательные) и опциональные (необязательные)
Имена аргументов
Опциональные аргументы отличаются от позиционных наличием префикса. По умолчанию в созданном экземпляре парсера префикс - '-' (дефис). Но можно установить и другой, или несколько префиксов одновременно
parser=argparse.ArgumentParser(prefix_chars='-#+')
parser.add_argument('p')
parser.add_argument('-op')
parser.add_argument('+opt')
parser.add_argument('#optional')
parser.print_help()
Позиционный аргумент задается строкой - имя аргумента опциональный - строкой (которой предшествует префикс) или последовательностбю строк (через запятую) - в этом случае перечисленная последовательность имен будет считаться синонимами одного аргумента. Теоретически argparse позволяет определить аргумент таким способом
parser=argparse.ArgumentParser(prefix_chars='-#+')
parser.add_argument('-o', '--option', '+opt', '+#-opti', action='store')
print parser.parse_args()
Все перечисленные комбинации префикс+имя являются синонимами одного аргумента, использование любой из них в командной строке будет давать один и тот же результат. Но вряд ли кому нужен такой зоопарк. Поэтому по принятому соглашению, опциональный аргумент задается кратким именем '-o' и (необязательно) полным '--option'
Псевдонимы аргументов
К добавленному агрументу необходимо так-то обращаться. Делаеся это через псевдоним аргумента. Для позиционного аргумента его псевдоним совпадает с именем. Для опационального по умолчанию псевдоним формируется так: отбрасывается префикс и выбирается (из одного или более) вариант имени, имеющий максимальную длину. Но мы можем самостоятельно задать псевдоним аргумента
parser=argparse.ArgumentParser()
parser.add_argument('-a', '--aaa', dest='b')
print parser.parse_args(['-a1'])
print parser.parse_args(['--aaa','1'])
Типы аргументов
Argparse позволяет определить тип значений аргумента, задаваемого в командной строке. При парсинге аргументов командной строки проверяется соответсвие значения аргумента заданному типу. По умолчанию предполагается, что значение добавляемого аргумента имеет строковый тип (string). В качестве альтернативы мы можем задать а) целочисленный тип (int), б) число с плавающей точкой (float), в) дескриптор файла (open), или с более тонкой настройкой - argparse.FileType() (см. документацию)
parser=argparse.ArgumentParser()
parser.add_argument('-i', type=int)
parser.add_argument('-f', type=float)
parser.add_argument('--file', type=open)
p=parser.parse_args('-i1 -f 1.6 --file test.txt'.split())
print p
for line in p.file:
print line
Но самое интересное - в качестве типа значения аргумента можно задать ссылку на функцию, которая будет принимать значение как строку, преобразовывать ее к нужному типу и возвращать результат в аргумент. Эту возможность удобно использовать, например, чтобы задать аргументу несколько именованных параметров и получить результат в аргумент в виде словаря
parser=argparse.ArgumentParser()
parser.add_argument("--params",
type=lambda kv: dict([i.split('=')for i in kv.split(" ")]))
print parser.parse_args(["--params", 'foo=6 bar=baz'])
Существует еще один полезный тип значений аргумента - ограниченный список. Если заданное в командной строке значение не принадлежит этому списку - будет сгенерировано исключение. Список допустимых значений задается в параметре choices, а параметр type задает тип значения каждого элемента списка (указывается, если все элементы принадлежат одному типу)
parser=argparse.ArgumentParser()
parser.add_argument('foo', type=int, choices=range(5, 10))
parser.add_argument('-a', choices=['string', 1])
print parser.parse_args(['6', '-a', 'string'])
Действия над аргументами
- Параметр вызова add_argument() 'action' указывает парсеру, как необходимо обрабатывать задаваемый аргумент и его значение. По умолчанию, значение аргумента просто сохраняется (store)
- Если значение аргумента всегда постоянно (константа) - пользователю нет необходимости каждый раз указывать в командной строке это значение, достаточно просто указать аргумент. Чтобы парсер в данном случае корректно отработал и сохранил константу как значение аргумента, 'action' присваивается значение 'store_const' и дополнительно определяется константа
parser = argparse.ArgumentParser()
parser.add_argument('--foo', action='store_const', const=42)
parser.parse_args(['--foo'])
Если аргумент не был указан в командной строке - в пространстве имен его значение - 'None'
Параметр 'const' (и 'default' тоже) в качестве значения могут принимать ссылки на функции. Это может быть полезным, вот пример
parser = argparse.ArgumentParser()
parser.add_argument('-a', nargs='+', type=int)
parser.add_argument('-s', action='store_const', const=sum, default=max)
p=parser.parse_args('-a 1 2 3'.split())
print p.s(p.a)
p=parser.parse_args('-a 1 2 3 -s'.split())
print p.s(p.a)
- Если же аргумент по сути своей - логическое значение и нам важно только знать был ли он указан при запуске скрипта в командной строке или нет - для action задаем значение 'store_true' или 'store_false'. Для 'store_true': есди аргумент был указан в командной строке - в пространстве имен ему присваивается значение 'True', если нет - 'False'. Для 'store_false' - соответственно все наоборот
- Если в командной строке могут быть заданы несколько значений одного аргумента, которые необходимо объединить в список, сделать это можно задав для action значение 'append'
parser = argparse.ArgumentParser()
parser.add_argument('--foo', action='append')
parser.parse_args('--foo 1 --foo 2'.split())
Реализовать такую обработку значений аргумента можно еще минимум двумя способами 1) определив функцию-обработчик в type добавляемого аргумента, 2) указав количесво значений nargs для аргумента (см. описание соответсвующих параметров)
- Если существует аргумент, значение для которого не предусмотрено и необходимо посчитать сколько раз этот аргумент был указан в командной строке - используем значение 'count'
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', '-v', action='count')
print parser.parse_args(['--verbose', '-v', '-vvv'])
Данное действие может быть полезно, если аргумент отождествляет, например, уровень. Тогда '-v' соотвествует первому уровню, '-vv' - второму и так далее.
Сущесвуют еще виды действий 'help' и 'version', посмотреть их описание можно в документации. Также, есди описанных типов действия недостаточно для описания поведения аргумента, можно создать собственное дейсвие, определив его через класс (см. документацию)
Количество значений опционального аргумента
По умолчанию количество значений аргумента равно 1. Но можно задать любое, определив их число через параметр nargs при добавлении аргумента.
Если количество значений для аргумента известно и фиксированно - в nargs указываем соответствующее число.
Если аргумент может быть а) указан в комадной строке с одним значением, б) указан без значения, в) не указан вовсе - задаем nargs='?'
parser = argparse.ArgumentParser()
parser.add_argument('-f', nargs='?', const='c', default='d')
print parser.parse_args()
print parser.parse_args(['-f'])
print parser.parse_args(['-f7'])
Если случай аналогичен случаю выше, но количетво аргументов может быть один и более - задаем nargs='*'. В данном случае применение 'const' недопустимо.
Еще один случай - если число значений аргумента должно быть не менее одного. В этом случае используется значение nargs='+'
Указание обязательных аргументов
Параметр 'required=True' - удобное дополнение, чтобы указать, что аргумент является обязательным для определения в командной строке. В принципе, реализовать данную проверку можно и без этого параметра, но он облегчает проверку
Получение аргументов и их значений
Чтобы при запуске скрипта получить набор заданных в командной строке аргументов и их значений, используется метод parse_args(). Ранее он неоднократно встречался в примерах.
Метод создает экземпляр класса Namespace, который включает все заданные в командной строке аргументы (позиционные и опциональные) и их значения. Доступ к значению аргумента (если он был задан в командной строке) осуществляется по его псевдониму
parser=argparse.ArgumentParser()
parser.add_argument('-a')
p=parser.parse_args()
print p.a
Получить значение всех аргументов (в том числе и не заданных в командной строке) можно через метод _get_kwargs()
parser=argparse.ArgumentParser()
parser.add_argument('-a')
parser.add_argument('-b')
p=parser.parse_args()
print p._get_kwargs()
Значения незаданных аргументов будут 'None'.
Протестировать из скрипта как будут распознаваться заданные аргументы и их значения можно так
parser=argparse.ArgumentParser()
parser.add_argument('-a')
parser.add_argument('-b')
parser.add_argument('с')
p=parser.parse_args(['c', '-a', '1','-bone'])
print p
Здесь несколько важных моментов работы парсера
- аргументы в командной строке можно задавать в любой последовательности
- значения опициональных аргументов можно НЕ отделять от имени аргумента пробелом
И вообще, опциональные аргументы в командной строке можно определять несколькими способами
- 'test.py -a 1'
- 'test.py -a1'
- 'test.py -a=1'
И еще метод parse_args() позволяет не полностью указывать имя аргумента, а только первую его часть. Если при этом не возникает конфликта имен. Например
parser = argparse.ArgumentParser()
parser.add_argument('-barcode')
parser.add_argument('-banner')
p=parser.parse_args()
print p
Вызов 'test.py -bar 1' правильно определит какой апгумент был задан в командной строке. А 'test.py -ba 1' приведет к ошибке конфликта имен
Еще Namespace может быть легко преобразован в словарь
parser = argparse.ArgumentParser()
parser.add_argument('-a', nargs='+', type=int)
p=parser.parse_args('-a 1 2 3'.split())
print vars(p)
Аргументы из файла
Argparse позволяет передавать скрипту значения аргументов из файлов. Это полезно когда а) при запуске скрипта в командную строку необходимо вводить большое количество аргументов, что неудобно, б) можно использовать как аналог конфигурационных файлов запуска скрипта с различными устойчивыми наборами параметров. Загрузка аргументов из файла выполняется так
parser = argparse.ArgumentParser(fromfile_prefix_chars='#')
parser.add_argument('-f', nargs=2)
parser.add_argument('--bar')
parser.add_argument('b')
p=parser.parse_args(['#arg.test'])
print p
При создании экземпляра парсера необходимо определить односимвольный префикс. Он будет указывать парсеру, что после этого префикса идет имя файла (а не очередной аргумент) из которого нужно получить набор аргументов для запуска скрипта.
Далее, в файле можно перечислять только те аргументы, которые добавлены в экземпляр парсера. правильно.
Правило составления файла с аргументами следующее: сначала идет псевдоним опционального аргумента (со всеми необходимыми префиксами), затем на следующей строке - его значение (если предусмотрено). Если аргумент предполагает несколько значений каждый указывается в новой строке. В остальном правила указания аргументов в файле (в том числе и их порядок) таакой же как и в командной строке.
Теперь при запуске скрипта достаточно только указать имя файла с аргументами.
Кстати ничто не мешает нам задавать аргументы параллельно в файле и командной строке. В данном случае парсер работает по следующему правилу: значение аргумента будет таким, который был задан последним. Тоесть если сначала указать имя файла с аргументами, а затем определить аргумент в командной строке, то аргументу будет присвоено значение из командной строки. И наоборот
И еще пара моментов. Описанные правила указания аргументов в файле определяются работой парсера по умолчанию. Вместе с тем argparse позволяет создать собственный парсер, задающий другие правила. Соответственно и порядок указания аргументов в файле изменится. Подробнее - в документации
Чтобы избежать исключение парсера, вызванного тем, что в файле определены аргументы не добавленные в парсер, в скрипте можно предусмотреть проверку. Делается это при помощи метода parse_known_args(). Например так
parser = argparse.ArgumentParser(fromfile_prefix_chars='#')
parser.add_argument('-a')
parser.add_argument('-b')
p=parser.parse_known_args(['#arg.test'])
print p
Метод возвращает кортеж, первый элемент которого - Namespace распознанных (добавленных в парсер) аргументов и их значений, которые были заданы при вызове скрипта (в командной с троке или в файле). Второй элемент - список нераспознанных аргументов и их значений
Команды
Их идея в следующем. Запускаемый скрипт может выполнять несколько задач, каждая из которых а) может быть описана отдельной командой, б) каждая команда имеет собственный набор аргументов. При этом, наборы аргументов различны, слабо пересекаются друг с другом или не пересекаются вовсе. Если это не так (все команды имеют одинаковой (почти) набор аргументов), то городить огород с выделением команд скрипта не имеет смысла. Приведем пример - скрипт работы с базой данных, который должен выполнять задачи:
- добавление записи в таблицу, задача 'add'
- просмотр записей таблицы по фильтру, задача 'view'.
Очевидно, что сначала нужно подключиться к бд. Значит необходим аргумент, задающий параметры подключения, назовем его '-p'. Этот аргумент необходим для всех задач.
Для задачи 'add' необходим аргумент '-d', задающий данные для полей добавляемой записи. А для задачи 'view' - аргумент, определяющий фильтр '-f'. Итого, для задач имеем набор общих аргументов ['-p'] и набор не пересекающихся ['-d'] и ['-f']. Реализация примерно такая
parser = argparse.ArgumentParser()
parser.add_argument('-p', required=True, help='Connection params (for all commands)')
# создаем подпарсер комманд пользователя. Внимание !! в парсер можно добавить только ОДИН подпарсер
user_subparsers = parser.add_subparsers(help='Script commands for user')
# добавляем команды и аргументы для каждой из них
parser_add = user_subparsers.add_parser('add', help="Discribe for command 'add'")
parser_add.add_argument('-d',help='add help')
parser_view = user_subparsers.add_parser('view', help="Discribe for command 'view'")
parser_view.add_argument('-f',help='view help')
parser.print_help()
print parser.parse_args(['-p', 'j', 'add'])
Команды являются обязательными. Определив в коде скрипта, что мы будем использовать команды, при вызове в командной строке мы обязаны указывать одну из них
Что нам дает применение команд - Более ясное понимание выполняемых действий. Если исключить команды у нас получится один общий набор атрибутов ['-p', '-d', '-f']. Причем для одного действия обязательной будет только часть комманд. Для другого - другая часть. В итоге, у пользователя скрипта может возникнуть непонимание, какое сочетание аргументов обязательно использовать, чтобы выполнить конкретное дейсвие. Команды помогают сориентироваться в наборе необходимых аргументов. А вывод справки
print parser.parse_args(['add', '-h'])
помогает понять какие аргументы необходимы для выполнения действия.
В итоге, используя команды, мы можем упростить код нашего скрипта. Поясним, используя пример выше Если мы реализуем запуск скрипта без команд, то чтобы определить, какое дейсвие хочет выполнить пользователь, необходимо проверить (if...else), какие документы он задал, и уже после этого запускать соотвествующий код.
Но если мы реализуем выполнение команд в скрипте, то для каждой из них можем задать функцию которая будет выполнять необходимый код, а задание проверки необходимых параметров возложить на argparse. Этот прием реализуется с помощью метода set_defaults()
Задание аргументов по умолчанию (set_defaults())
Метод задает для парсера (или команды) арументы и их значения, которые будут добавлены в Namespace автоматически, без необходимости их определения пользователем. В качестве значения аргумента по умолчанию мы можем добавить и функцию, которая будет выполнять необходимые действия.
Таким образом определение и выполнение намерений пользователя с помощью команд может выглядеть так(в качестве примера напишем скрипт решаюший две задачи: 1) возведение числа в степень и 2) получение натурального логарифма из числа. Для обоих задач существует обший обязательный аргумент - число. Кроме того, для задачи (1) должен быть определен дополниельный обязательный аргумент - степень (число с плавающей точкой). Для задачи (2) нужен аргумент основание логарифма).
def pow(args):
return args.d**args.s
import math
def log(args):
return math.log(args.d,args.b)
if __name__=='__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-d', required=True, type=int)
subparsers = parser.add_subparsers()
parser_pow = subparsers.add_parser('pow')
parser_pow.add_argument('-s', required=True, type=float)
parser_pow.set_defaults(f=pow)
parser_log = subparsers.add_parser('log')
parser_log.add_argument('-b', type=float)
parser_log.set_defaults(f=log)
p=parser.parse_args(['-d 2', 'pow', '-s 3'])
print p.f(p)
p=parser.parse_args(['-d 2', 'log', '-b 3'])
print p.f(p)
Т.е., применяя команды отпрадает необходимость писать проверки на необходимые аргументы - эту задачу выполняет argparse
В статье рассмотрены основные (не все) водзможности использования argparse. Подробнее - в документации (англ.)