Руководство по argparse
- автор:
-
Tshepang Mbambo
Это руководство — несложное введение в 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". Это означает, что при указании параметра переменнойargs.verboseприсваивается значениеTrue. Если параметр не указан, подразумевается значение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
- Мы вернули позиционный аргумент, поэтому программа выдала сообщение об ошибке.
- Обратите внимание, что порядок не имеет значения.
Давайте вернём программе возможность принимать несколько значений уровня подробности и действительно воспользуемся ею:
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
Обратите внимание: изменение отражено и в сообщении об ошибке, и в справочном тексте.
Теперь попробуем другой, довольно распространённый способ управления подробностью вывода. Он также соответствует тому, как исполняемый файл 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(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, чтобы его можно было сравнивать с другими целыми числами. Помните: по умолчанию, если необязательный аргумент не указан, он получает значение None, которое нельзя сравнить с целым числом (отсюда исключение 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
До сих пор мы использовали уровень подробности, чтобы изменять выводимый текст. В следующем примере уровень подробности используется для того, чтобы выводить больше текста:
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 позволяет задавать пользовательские преобразователи типов для аргументов командной строки. С их помощью можно изменять введённые пользователем данные до сохранения в argparse.Namespace. Это может быть полезно, если перед использованием данных в программе нужно предварительно их обработать.
Для пользовательского преобразователя типа подойдёт любой вызываемый объект, который принимает один строковый аргумент (значение аргумента) и возвращает преобразованное значение. Однако если нужно обрабатывать более сложные случаи, вместо этого можно использовать пользовательский класс действия с параметром action.
Например, предположим, что нужно обрабатывать аргументы с разными префиксами по-разному:
import argparse
parser = argparse.ArgumentParser(prefix_chars='-+')
parser.add_argument('-a', metavar='<value>', action='append',
type=lambda x: ('-', x))
parser.add_argument('+a', metavar='<value>', action='append',
type=lambda x: ('+', x))
args = parser.parse_args()
print(args)
Результат:
$ python prog.py -a value1 +a value2
Namespace(a=[('-', 'value1'), ('+', 'value2')])
В этом примере мы:
- Создали парсер с пользовательскими символами префиксов с помощью параметра
prefix_chars. - Определили два аргумента —
-aи+a, — для которых с помощью параметраtypeсоздали пользовательские преобразователи типов, сохраняющие значение в кортеже вместе с префиксом.
Без пользовательских преобразователей типов аргументы -a и +a считались бы одним и тем же аргументом, что было бы нежелательно. Пользовательские преобразователи типов позволили нам различать эти аргументы.
Заключение
Модуль argparse предлагает гораздо больше возможностей, чем рассмотренные здесь. Его документация подробна и содержит много примеров. После этого руководства вам будет легко разобраться в ней, не чувствуя себя перегруженными.
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/howto/argparse.html