(Изображение (C) Тай Кедзиерски)
Краткий справочник Python ArgParse
Краткое руководство по разбору аргументов — и предложение по модели реализации подкоманд.
Разбор аргументов — краткая справка
Общее определение аргумента (из стандартной документации), аннотированное.
Отметим, что аргумент type=
на самом деле является вызываемым обработчиком и может быть любой функцией или даже конструктором. Таким образом, вы гарантируете, что аргумент будет немедленно преобразован в нужный вам тип; все, что затем использует этот аргумент, может получить в результате полностью экстраполированный аргумент, без необходимости откладывать интерпретацию аргумента.
parser.add_argument(
'integers', # Positional argument name
metavar='N', # will be actually accessed in "parsed_args.N"
# instead of "parsed_args.integers"
type=int, # actually a type converter - the callable must take 1 argument,
# the value, and return the value coerced to the correct type
nargs='+', # Specifies that there must be one to any number of these
help='an integer for the accumulator' # A help string for "--help" option
)
Если вы хотите, чтобы опция на самом деле была флагом, вы можете указать вместо нее действие:
parsers.add_argument(
# Optional argument name
"--say-hi",
# An action spec. See also store_const
# this populates the parsed_args.say_hi with True
# if specified, but defaulting to False otherwise
action="store_true"
)
# If no action spec is provided, the optional argument
# specifies that it needs to take a value
parsers.add_argument(
"--config", # optional argument
"-c", # shorthand
)
# Essentially expects `... --config SOME_VALUE ...`
Подпарсеры
Ваш сценарий может принимать подкоманды — это ситуация, когда при вызове вашего сценария необходимо выполнить определенный тип действия, каждый из которых имеет свое собственное дерево аргументов. Подкоманды могут иметь свои собственные подкоманды.
Например, инструмент awscli
:
- команда верхнего уровня —
aws
. - для взаимодействия с S3 мы используем подкоманду
s3
:aws s3
- мы можем захотеть либо загрузить, либо выгрузить:
aws s3 upload
.
Базовый пример реализации функции разбора аргументов с подпарсерами (для подкоманд):
def parse_app_args(arguments=None):
# Create a top-level parser
parser = argparse.ArgumentParser()
# Immediately create a subparser holder for a sub-command.
# The sub-command's string will be stored in "parsed_arguments.cmd"
# of the parsed arguments' namespeace object
subparsers = parser.add_subparsers(dest="cmd")
# Now we have the holder, add actual parsers to it
# The first subcommand. When arguments are parsed, the value "status"
# would be found in "parsed_args.cmd" , because it is a named parser for the
# subparser collection with `dest="cmd"` from above
subparser.add_parser("status")
# We add a second subcommand, whose value is an alternative
# available to `cmd`
subp_power = subparsers.add_parser("power")
# Sub-arguments - uses the same function signature as described
# in the documentation for top-level "add_parser"
# Note the use of `choice=` to force specific raw values
subp_power.add_argument("state", choices=["on", "off"])
# parse_app_args implicitly parses sys.argv[1:]
# if arguments==None
# but you could also parse a custom list of args as well...
return parser.parse_args(arguments)
parsed_args = parse_app_args()
print(parsed_args)
if parsed_args.cmd == "status":
print_status()
elif parsed_args.cmd == "power":
set_power(parsed_args.state)
Предложение по реализации перехода от подкоманды к модулю
Часто вы обнаружите, что вам нужно несколько действий для вашего приложения — именно тогда вы можете реализовать подкоманды и использовать подпарсеры.
На этом этапе стоит подумать о том, что если вам не нравятся большие, громоздкие файлы или прыжки вокруг, пытаясь соединить парсеры аргументов в одном файле с соответствующими модулями в другом, то, возможно, вам стоит быть немного более стратегичным в реализации.
В следующем примере мы реализуем
- основную логику и базовый парсер в файле
main.py
. - но специально позволяем модулям отвечать как за
- аргументы, которые они ожидают для своих подкоманд
- их фактическую реализацию.
- все модули предоставляют методы
setup_args(subparser)
иrun(args)
.
main.py
#!/usr/bin/env python3
import argparse
# Some fictional machine API - split the logic into modules
import power
import engine
def parse_app_args(args=None):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="cmd")
# Farming out the subparser definitionss to their respective modules
# So each module can define its parsing options itself
power.setup_args(subparsers)
engine.setup_args(subparsers)
return parser.parse_args(args)
def main():
parsed_args = parse_app_args()
# We make a point of moving subcommand implementations to their own files,
# to decluttrer this main file
command_map = {
"power": power.run,
"engine": engine.run,
}
# Because the parser will only accept values for the named subparser,
# we can consider the check has already been done for us :-)
command_map[parsed_args.cmd](parsed_args)
if __name__ == "__main__":
main()
engine.py
def setup_args(subparsers):
subp_engine = subparsers.add_parser("engine")
subp_engine.add_argument("speed", type=int)
def run(args):
print("Setting engine speed: {}".format(args.speed))
power.py
def setup_args(subparsers):
subp_power = subparsers.add_parser("power")
subp_power.add_argument("state", choices=["on", "off"])
def run(args):
print("Setting power state: {}".format(args.state))
Сделать один аргумент зависимым от другого
Подпарсеры полезны, когда необходимо указать взаимоисключающие подкоманды.
Однако в некоторых случаях один аргумент нужно указывать только в том случае, если был задан основной аргумент, а два основных аргумента должны сосуществовать.
Например, я могу иметь инструмент для запуска демо-сервера:
standup.py [--server-1 --config-1 CONFIG1] [--server-2 --config-2 CONFIG2]
После ответа на StackOverflow по этой проблеме кажется, что необходимо использовать два шага парсера.
# Add a checker to set the required flags
checker = argparse.ArgumentParser()
checker.add_argument("--server-1", action="store_true")
checker.add_argument("--server-2", action="store_true")
checks, _ = checker.parse_known_args()
# Create a new main parser, inheriting the argument definitions
# from the checker, to not duplicate definitions.
parser = argparse.ArgumentParser(parents=[checker])
# Use the flags from `checker` to flip the requirement state
# as needed
parser.add_argument("--config-1", required=checks.server_1)
parser.add_argument("--config-2", required=checks.server_2)
parsed_args = parser.parse_args()