Учебник по Argparse

Автор:Tshepang Lekhonkhobe

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

Примечание

Есть два других модуля, которые выполняют одну и ту же задачу, а именно: getopt (эквивалентный getopt() из языка C) и устаревший optparse. Отметим также, что argparse основан на optparse и, следовательно, очень похож с точки зрения использования.

Понятия

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

$ ls
cpython  devguide  prog.py  pypy  rm-unused-function.patch
$ ls pypy
ctypes_configure  demo  dotviewer  include  lib_pypy  lib-python ...
$ ls -l
total 20
drwxr-xr-x 19 wena wena 4096 Feb 18 18:51 cpython
drwxr-xr-x  4 wena wena 4096 Feb  8 12:04 devguide
-rwxr-xr-x  1 wena wena  535 Feb 19 00:05 prog.py
drwxr-xr-x 14 wena wena 4096 Feb  7 00:59 pypy
-rw-r--r--  1 wena wena  741 Feb 18 01:01 rm-unused-function.patch
$ ls --help
Usage: ls [OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.
...

Несколько концепций, которые мы можем извлечь из четырех команд:

  • Команда ls полезна при запуске без каких-либо параметров. По умолчанию отображается содержимое текущего каталога.
  • Если мы хотим больше того, что он предоставляет по умолчанию, мы говорим это немного больше. В этом случае мы хотим, чтобы он отображал другой каталог, pypy. Мы указали, что называется позиционным аргументом. Он назван так, потому что программа должна знать, что делать со значением, исключительно на основе того, где оно появляется в командной строке. Эта концепция более актуальна для такой команды, как cp, наиболее основное использование которой является cp SRC DEST. Первая позиция то что вы хотите скопированный и вторая позиция куда, вы хотите это, скопировать.
  • Скажем, мы хотим изменить поведение программы. В нашем примере мы показываем больше информации для каждого файла вместо того, чтобы просто показывать имена файлов. В этом случае -l называется необязательным аргументом.
  • Это фрагмент текста справки. Это очень полезно в том, что вы можете наткнуться на программу, которую вы никогда раньше не используемый, и можете выяснить, как она работает, просто прочитав ее справочный текст.

Основы

Начнем с очень простого примера, который (почти) ничего не делает:

import argparse
parser = argparse.ArgumentParser()
parser.parse_args()

Ниже приведен результат выполнения кода:

$ python3 prog.py
$ python3 prog.py --help
usage: prog.py [-h]

optional arguments:
  -h, --help  show this help message and exit
$ python3 prog.py --verbose
usage: prog.py [-h]
prog.py: error: unrecognized arguments: --verbose
$ python3 prog.py foo
usage: prog.py [-h]
prog.py: error: unrecognized arguments: foo

Вот что происходит:

  • Выполнение сценария без каких-либо параметров не приводит к отображению stdout. Не так полезно.
  • Второй начинает отображать полезность модуля argparse. Мы почти ничего не сделали, но уже получили хорошее сообщение о помощи.
  • Опция --help, которая также может быть сокращена до -h, является единственной опцией, которую мы получаем бесплатно (т.е. нет необходимости указывать ее). Указание чего-либо другого приводит к ошибке. Но даже в этом случае мы получаем полезное сообщение об использовании, также бесплатно.

Представление позиционных аргументов

Пример:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo")
args = parser.parse_args()
print(args.echo)

И запуск кода:

$ python3 prog.py
usage: prog.py [-h] echo
prog.py: error: the following arguments are required: echo
$ python3 prog.py --help
usage: prog.py [-h] echo

positional arguments:
  echo

optional arguments:
  -h, --help  show this help message and exit
$ python3 prog.py foo
foo

Вот что происходит:

  • Мы добавили метод add_argument(), который мы используем для указания параметров командной строки, которые программа готова принять. В этом случае я назвал это echo так, чтобы это соответствовало своей функции.
  • Вызов программы требует указания опции.
  • На самом деле parse_args() метод возвращает некоторые данные из указанных опций, в данном случае echo.
  • Переменная является некоторой формой «магии», которую argparse выполняет бесплатно (т.е. нет необходимости указывать, в какой переменной хранится это значение). Вы также заметите, что его имя совпадает с аргументом строка, предоставленным методу echo.

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

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("echo", help="echo the string you use here")
args = parser.parse_args()
print(args.echo)

И получаем:

$ python3 prog.py -h
usage: prog.py [-h] echo

positional arguments:
  echo        echo the string you use here

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

Теперь, как насчет сделать что-то еще более полезным:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", help="display a square of a given number")
args = parser.parse_args()
print(args.square**2)

Ниже приведен результат выполнения кода:

$ python3 prog.py 4
Traceback (most recent call last):
  File "prog.py", line 5, in <module>
    print(args.square**2)
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'

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

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", help="display a square of a given number",
                    type=int)
args = parser.parse_args()
print(args.square**2)

Ниже приведен результат выполнения кода:

$ python3 prog.py 4
16
$ python3 prog.py four
usage: prog.py [-h] square
prog.py: error: argument square: invalid int value: 'four'

Все прошло хорошо. Теперь программа даже помогает выйти из-за неправильного нелегального ввода перед продолжением.

Представление дополнительных аргументов

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

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbosity", help="increase output verbosity")
args = parser.parse_args()
if args.verbosity:
    print("verbosity turned on")

И вывод:

$ python3 prog.py --verbosity 1
verbosity turned on
$ python3 prog.py
$ python3 prog.py --help
usage: prog.py [-h] [--verbosity VERBOSITY]

optional arguments:
  -h, --help            show this help message and exit
  --verbosity VERBOSITY
                        increase output verbosity
$ python3 prog.py --verbosity
usage: prog.py [-h] [--verbosity VERBOSITY]
prog.py: error: argument --verbosity: expected one argument

Вот что происходит:

  • Программа записывается так, чтобы отображать что-то, когда --verbosity указан и ничего не отображать, когда нет.
  • Чтобы показать, что этот параметр является на самом деле необязательным, нет ошибки при запуске программы без него. Обратите внимание, что по умолчанию, если дополнительный аргумент не используется, соответствующей переменной, в этом случае args.verbosity, дают None как стоимость, которая является причиной, это проваливает тест правды if инструкция.
  • Справочное сообщение немного отличается.
  • При использовании опции --verbosity необходимо также указать некоторое значение, любое значение.

Вышеприведённый пример принимает произвольные целочисленные значения для --verbosity, но для нашей простой программы на самом деле полезны только два значения, True или False. Давайте изменим код соответствующим образом:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--verbose", help="increase output verbosity",
                    action="store_true")
args = parser.parse_args()
if args.verbose:
    print("verbosity turned on")

И вывод:

$ python3 prog.py --verbose
verbosity turned on
$ python3 prog.py --verbose 1
usage: prog.py [-h] [--verbose]
prog.py: error: unrecognized arguments: 1
$ python3 prog.py --help
usage: prog.py [-h] [--verbose]

optional arguments:
  -h, --help  show this help message and exit
  --verbose   increase output verbosity

Вот что происходит:

  • Этот параметр теперь больше флага, чем того, для чего требуется значение. Мы даже изменили название варианта, чтобы соответствовать этой идее. Обратите внимание, что теперь мы задаем новое ключевое слово action и придадим ему значение "store_true". Это означает, что, если опция указана, присвойте значение True параметру args.verbose. Не указание этого подразумевает False.
  • Он жалуется, когда вы указываете значение, в истинном духе того, что на самом деле флаги.
  • Обратите внимание на другой текст справки.

Короткие опции

Если вы знакомы с использованием командной строки, вы заметите, что я еще не коснулся темы коротких версий параметров. Это довольно просто:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-v", "--verbose", help="increase output verbosity",
                    action="store_true")
args = parser.parse_args()
if args.verbose:
    print("verbosity turned on")

И здесь goes:

$ python3 prog.py -v
verbosity turned on
$ python3 prog.py --help
usage: prog.py [-h] [-v]

optional arguments:
  -h, --help     show this help message and exit
  -v, --verbose  increase output verbosity

Обратите внимание, что новая возможность также отражена в тексте справки.

Объединение позиционных и необязательных аргументов

Наша программа постоянно усложняется:

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)

А теперь выход:

$ python3 prog.py
usage: prog.py [-h] [-v] square
prog.py: error: the following arguments are required: square
$ python3 prog.py 4
16
$ python3 prog.py 4 --verbose
the square of 4 equals 16
$ python3 prog.py --verbose 4
the square of 4 equals 16
  • Мы вернули позиционный спор, отсюда и жалоба.
  • Обратите внимание, что заказ не имеет значения.

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

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

И вывод:

$ python3 prog.py 4
16
$ python3 prog.py 4 -v
usage: prog.py [-h] [-v VERBOSITY] square
prog.py: error: argument -v/--verbosity: expected one argument
$ python3 prog.py 4 -v 1
4^2 == 16
$ python3 prog.py 4 -v 2
the square of 4 equals 16
$ python3 prog.py 4 -v 3
16

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

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display a square of a given number")
parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2],
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
    print("the square of {} equals {}".format(args.square, answer))
elif args.verbosity == 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

И вывод:

$ python3 prog.py 4 -v 3
usage: prog.py [-h] [-v {0,1,2}] square
prog.py: error: argument -v/--verbosity: invalid choice: 3 (choose from 0, 1, 2)
$ python3 prog.py 4 -h
usage: prog.py [-h] [-v {0,1,2}] square

positional arguments:
  square                display a square of a given number

optional arguments:
  -h, --help            show this help message and exit
  -v {0,1,2}, --verbosity {0,1,2}
                        increase output verbosity

Обратите внимание, что изменение также отражает обоих в сообщении об ошибке, а также помощи строка.

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

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

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

$ python3 prog.py 4
16
$ python3 prog.py 4 -v
4^2 == 16
$ python3 prog.py 4 -vv
the square of 4 equals 16
$ python3 prog.py 4 --verbosity --verbosity
the square of 4 equals 16
$ python3 prog.py 4 -v 1
usage: prog.py [-h] [-v] square
prog.py: error: unrecognized arguments: 1
$ python3 prog.py 4 -h
usage: prog.py [-h] [-v] square

positional arguments:
  square           display a square of a given number

optional arguments:
  -h, --help       show this help message and exit
  -v, --verbosity  increase output verbosity
$ python3 prog.py 4 -vvv
16
  • Да, теперь это больше флаг (похожий на action="store_true") в предыдущей версии нашего сценария. Это должно объяснить жалобу.
  • Он также ведет себя как «store_true» действие.
  • Вот демонстрация того, что дает действие «count.» вероятно, вы уже видели подобное использование.
  • И если вы не указываете флаг -v, этот флаг считается имеющим значение None.
  • Как и следовало ожидать, указывая длинную форму флага, мы должны получить тот же вывод.
  • К сожалению, наши выходные данные справки не очень информативны в отношении новой способности, которую приобрел наш сценарий, но это всегда можно исправить, улучшив документацию для нашего сценария (например, с помощью аргумента help ключевой).
  • Последний вывод раскрывает ошибку в нашей программе.

Давайте исправим это:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square", type=int,
                    help="display a square of a given number")
parser.add_argument("-v", "--verbosity", action="count",
                    help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2

# bugfix: replace == with >=
if args.verbosity >= 2:
    print("the square of {} equals {}".format(args.square, answer))
elif args.verbosity >= 1:
    print("{}^2 == {}".format(args.square, answer))
else:
    print(answer)

И вот что она дает:

$ python3 prog.py 4 -vvv
the square of 4 equals 16
$ python3 prog.py 4 -vvvv
the square of 4 equals 16
$ python3 prog.py 4
Traceback (most recent call last):
  File "prog.py", line 11, in <module>
    if args.verbosity >= 2:
TypeError: '>=' not supported between instances of 'NoneType' and 'int'
  • Первый выход прошел хорошо, и исправляет ошибку, которая была у нас раньше. То есть мы хотим, чтобы любое значение > = 2 было максимально подробным.
  • Третий выход не так хорош.

Давайте исправим эту ошибку:

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

Мы только что ввели еще одно ключевое слово, default. Мы установили его на 0, чтобы сделать его сопоставимым с другими значениями int. Помните, что по умолчанию, если необязательный аргумент не указан, он получает значение None, которое не может сравниваться со значением int (следовательно, исключение TypeError).

И:

$ python3 prog.py 4
16

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

Становлюсь немного продвинутее

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

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
parser.add_argument("-v", "--verbosity", action="count", default=0)
args = parser.parse_args()
answer = args.x**args.y
if args.verbosity >= 2:
    print("{} to the power {} equals {}".format(args.x, args.y, answer))
elif args.verbosity >= 1:
    print("{}^{} == {}".format(args.x, args.y, answer))
else:
    print(answer)

Вывод:

$ python3 prog.py
usage: prog.py [-h] [-v] x y
prog.py: error: the following arguments are required: x, y
$ python3 prog.py -h
usage: prog.py [-h] [-v] x y

positional arguments:
  x                the base
  y                the exponent

optional arguments:
  -h, --help       show this help message and exit
  -v, --verbosity
$ python3 prog.py 4 2 -v
4^2 == 16

Заметьте, что до сих пор мы использовали уровень многословия для change текст, который показан. Следующий пример вместо этого использует уровень многословия, чтобы показать текст more вместо этого:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
parser.add_argument("-v", "--verbosity", action="count", default=0)
args = parser.parse_args()
answer = args.x**args.y
if args.verbosity >= 2:
    print("Running '{}'".format(__file__))
if args.verbosity >= 1:
    print("{}^{} == ".format(args.x, args.y), end="")
print(answer)

Вывод:

$ python3 prog.py 4 2
16
$ python3 prog.py 4 2 -v
4^2 == 16
$ python3 prog.py 4 2 -vv
Running 'prog.py'
4^2 == 16

Конфликтующие опции

Пока мы работаем с двумя методы argparse.ArgumentParser сущность. Давайте представим третий, add_mutually_exclusive_group(). Это позволяет нам указывать варианты, которые конфликтуют друг с другом. Давайте также изменим остальную часть программы, чтобы новый функционал имел больше смысла: введем опцию --quiet, которая будет противоположна --verbose:

import argparse

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
args = parser.parse_args()
answer = args.x**args.y

if args.quiet:
    print(answer)
elif args.verbose:
    print("{} to the power {} equals {}".format(args.x, args.y, answer))
else:
    print("{}^{} == {}".format(args.x, args.y, answer))

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

$ python3 prog.py 4 2
4^2 == 16
$ python3 prog.py 4 2 -q
16
$ python3 prog.py 4 2 -v
4 to the power 2 equals 16
$ python3 prog.py 4 2 -vq
usage: prog.py [-h] [-v | -q] x y
prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
$ python3 prog.py 4 2 -v --quiet
usage: prog.py [-h] [-v | -q] x y
prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose

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

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

import argparse

parser = argparse.ArgumentParser(description="calculate X to the power of Y")
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("x", type=int, help="the base")
parser.add_argument("y", type=int, help="the exponent")
args = parser.parse_args()
answer = args.x**args.y

if args.quiet:
    print(answer)
elif args.verbose:
    print("{} to the power {} equals {}".format(args.x, args.y, answer))
else:
    print("{}^{} == {}".format(args.x, args.y, answer))

Обратите внимание на небольшое различие в тексте использования. Обратите внимание на [-v | -q], который говорит нам, что мы можем использовать -v или -q, но не оба одновременно:

$ python3 prog.py --help
usage: prog.py [-h] [-v | -q] x y

calculate X to the power of Y

positional arguments:
  x              the base
  y              the exponent

optional arguments:
  -h, --help     show this help message and exit
  -v, --verbose
  -q, --quiet

Заключение

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