Учебное пособие по разбору аргументов¶
- автор:
Чепанг Лекхонкхобе
Это руководство предназначено для краткого ознакомления с argparse
, рекомендуемым модулем синтаксического анализа командной строки в стандартной библиотеке Python.
Примечание
Есть два других модуля, которые выполняют ту же задачу, а именно getopt
(эквивалент getopt()
из языка Си) и устаревший 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
Вот что происходит на самом деле:
Запуск скрипта без каких-либо опций приводит к тому, что в стандартном выводе ничего не отображается. Не очень полезно.
Второй начинает показывать полезность модуля
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 обрабатывает свой собственный аргумент многословия (проверьте вывод 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)
Мы ввели еще одно действие, «подсчет», для подсчета количества повторений определенных опций.
$ 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(f"{args.x} to the power {args.y} equals {answer}")
else:
print(f"{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¶
Выходные данные модуля 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
предлагает гораздо больше, чем показано здесь. Его документы довольно подробные и обстоятельные, а также полны примеров. Ознакомившись с этим руководством, вы должны легко усвоить их, не чувствуя себя перегруженным.