Разбор аргументов и подпарсеры в Python

(Изображение (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 ...`
Enter fullscreen mode Выйти из полноэкранного режима

Подпарсеры

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

Например, инструмент 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()
Вход в полноэкранный режим Выход из полноэкранного режима

Оцените статью
Procodings.ru
Добавить комментарий