Subcommands

argparse · Practical · 12 min

Any tool with more than one job (init, run, deploy) is easier to use with subcommands.

Tools like git commit and pip install use subcommands: the first positional chooses a command, and each command has its own arguments.

sub = parser.add_subparsers(dest="command", required=True)
run = sub.add_parser("run", help="run the app")
run.add_argument("--port", type=int, default=8000)

add_subparsers() returns a special action; each add_parser(name) returns an ordinary ArgumentParser for that command. dest="command" stores which command was chosen, and required=True makes omitting it an error (subcommands are optional by default).

The neat dispatch idiom is set_defaults: attach the handler function to each subparser and call it after parsing.

run.set_defaults(func=cmd_run)
args = parser.parse_args()
args.func(args)

Useful extras:

  • add_parser("install", aliases=["i"]) for short names,
  • options defined on the main parser go before the subcommand (tool -v run), while subparser options go after it,
  • shared options can be added via parent parsers (an expert lesson),
  • each subparser gets its own --help: tool run --help.

Example

import argparse

def cmd_init(args):
    print(f"init in {args.path}")

def cmd_run(args):
    print(f"run on port {args.port}, verbose={args.verbose}")

p = argparse.ArgumentParser(prog="app")
p.add_argument("-v", "--verbose", action="store_true")
sub = p.add_subparsers(dest="command", required=True)

init = sub.add_parser("init", help="create a project")
init.add_argument("path", nargs="?", default=".")
init.set_defaults(func=cmd_init)

run = sub.add_parser("run", aliases=["r"], help="start the server")
run.add_argument("--port", type=int, default=8000)
run.set_defaults(func=cmd_run)

for argv in (["init"], ["-v", "r", "--port", "9000"]):
    args = p.parse_args(argv)
    args.func(args)
# init in .
# run on port 9000, verbose=True

Remember

  • add_subparsers() + add_parser(name) gives each command its own parser
  • Use dest= and required=True to record and require the command
  • set_defaults(func=handler) makes dispatch a one-liner

Watch out for

  • Putting a main-parser option after the subcommand name, where the subparser doesn't recognise it.

Check yourself

  1. Given main parser option -v and subcommand run, which command line works?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/argparse.html#sub-commands