Учебник по Argparse

автор

Тшепанг Лехонкхобе

Этот учебник предназначен для мягкого введения в 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]

options:
  -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

options:
  -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

options:
  -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]

options:
  -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]

options:
  -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")

И вот:

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

options:
  -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(f"the square of {args.square} equals {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(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
    print(f"{args.square}^2 == {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(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
    print(f"{args.square}^2 == {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

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

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

Теперь воспользуемся другим подходом к игре с многословностью, который является довольно распространенным. Он также соответствует тому, как исполняемый файл CPython обрабатывает свой собственный аргумент verbosity (проверьте вывод 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(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
    print(f"{args.square}^2 == {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

options:
  -h, --help       show this help message and exit
  -v, --verbosity  increase output verbosity
$ python3 prog.py 4 -vvv
16
  • Да, теперь это скорее флаг (аналогичный action="store_true") в предыдущей версии нашего скрипта. Это должно объяснить жалобу.

  • Он также ведет себя аналогично действию «store_true».

  • Вот демонстрация того, что дает действие «подсчитать». Вероятно, вы уже сталкивались с подобным использованием.

  • И если вы не указали флаг -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(f"the square of {args.square} equals {answer}")
elif args.verbosity >= 1:
    print(f"{args.square}^2 == {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(f"the square of {args.square} equals {answer}")
elif args.verbosity >= 1:
    print(f"{args.square}^2 == {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(f"{args.x} to the power {args.y} equals {answer}")
elif args.verbosity >= 1:
    print(f"{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

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

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

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(f"Running '{__file__}'")
if args.verbosity >= 1:
    print(f"{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(f"{args.x} to the power {args.y} equals {answer}")
else:
    print(f"{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

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

Заключение

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

Вернуться на верх