Самоучитель по 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()

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

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

options:
  -h, --help  show this help message and exit
$ python prog.py --verbose
usage: prog.py [-h]
prog.py: error: unrecognized arguments: --verbose
$ python 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)

И запустить код:

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

positional arguments:
  echo

options:
  -h, --help  show this help message and exit
$ python 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)

И мы получаем:

$ python 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)

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

$ python 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)

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

$ python prog.py 4
16
$ python 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")

И выход:

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

options:
  -h, --help            show this help message and exit
  --verbosity VERBOSITY
                        increase output verbosity
$ python 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")

И выход:

$ python prog.py --verbose
verbosity turned on
$ python prog.py --verbose 1
usage: prog.py [-h] [--verbose]
prog.py: error: unrecognized arguments: 1
$ python 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")

И вот:

$ python prog.py -v
verbosity turned on
$ python 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)

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

$ python prog.py
usage: prog.py [-h] [-v] square
prog.py: error: the following arguments are required: square
$ python prog.py 4
16
$ python prog.py 4 --verbose
the square of 4 equals 16
$ python prog.py --verbose 4
the square of 4 equals 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,
                    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)

И выход:

$ python prog.py 4
16
$ python prog.py 4 -v
usage: prog.py [-h] [-v VERBOSITY] square
prog.py: error: argument -v/--verbosity: expected one argument
$ python prog.py 4 -v 1
4^2 == 16
$ python prog.py 4 -v 2
the square of 4 equals 16
$ python 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)

И выход:

$ python 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)
$ python 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

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

Теперь давайте воспользуемся другим подходом к игре со значением 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», для подсчета количества вхождений определенных опций.

$ python prog.py 4
16
$ python prog.py 4 -v
4^2 == 16
$ python prog.py 4 -vv
the square of 4 equals 16
$ python prog.py 4 --verbosity --verbosity
the square of 4 equals 16
$ python prog.py 4 -v 1
usage: prog.py [-h] [-v] square
prog.py: error: unrecognized arguments: 1
$ python 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
$ python 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(f"the square of {args.square} equals {answer}")
elif args.verbosity >= 1:
    print(f"{args.square}^2 == {answer}")
else:
    print(answer)

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

$ python prog.py 4 -vvv
the square of 4 equals 16
$ python prog.py 4 -vvvv
the square of 4 equals 16
$ python 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).

И:

$ python 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)

Выход:

$ python prog.py
usage: prog.py [-h] [-v] x y
prog.py: error: the following arguments are required: x, y
$ python 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
$ python prog.py 4 2 -v
4^2 == 16

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

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)

Выход:

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

Указание неоднозначных аргументов

Когда неясно, является ли аргумент позиционным или для аргумента, -- можно использовать, чтобы сказать parse_args(), что все последующее - позиционный аргумент:

>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('-n', nargs='+')
>>> parser.add_argument('args', nargs='*')

>>> # ambiguous, so parse_args assumes it's an option
>>> parser.parse_args(['-f'])
usage: PROG [-h] [-n N [N ...]] [args ...]
PROG: error: unrecognized arguments: -f

>>> parser.parse_args(['--', '-f'])
Namespace(args=['-f'], n=None)

>>> # ambiguous, so the -n option greedily accepts arguments
>>> parser.parse_args(['-n', '1', '2', '3'])
Namespace(args=[], n=['1', '2', '3'])

>>> parser.parse_args(['-n', '1', '--', '2', '3'])
Namespace(args=['2', '3'], n=['1'])

Противоречивые варианты

До сих пор мы работали с двумя методами экземпляра 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}")

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

$ python prog.py 4 2
4^2 == 16
$ python prog.py 4 2 -q
16
$ python prog.py 4 2 -v
4 to the power 2 equals 16
$ python 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
$ python 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(f"{args.x} to the power {args.y} equals {answer}")
else:
    print(f"{args.x}^{args.y} == {answer}")

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

$ python 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

Вывод модуля argparse, такой как текст справки и сообщения об ошибках, можно перевести с помощью модуля gettext. Это позволяет приложениям легко локализовать сообщения, выдаваемые argparse. См. также Интернационализация ваших программ и модулей.

Например, в этом выходе argparse:

$ python 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

Строки usage:, positional arguments:, options: и show this help message and exit можно перевести.

Чтобы перевести эти строки, их нужно сначала извлечь в файл .po. Например, используя Babel, выполните эту команду:

$ pybabel extract -o messages.po /usr/lib/python3.12/argparse.py

Эта команда извлечет все переводимые строки из модуля argparse и выведет их в файл с именем messages.po. Эта команда предполагает, что ваш Python установлен в /usr/lib.

Вы можете узнать расположение модуля argparse в вашей системе с помощью этого скрипта:

import argparse
print(argparse.__file__)

Когда сообщения в файле .po будут переведены и переводы установлены с помощью gettext, argparse сможет отображать переведенные сообщения.

Чтобы перевести собственные строки в вывод argparse, используйте gettext.

Заключение

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