How to Build a Subcommand Parser Tree with argparse in Python

Create a CLI with nested subcommands (like git) using argparse subparsers, where each subcommand maps to its own handler function.

Medium Python 3.7+ Aug 9, 2026 Functions & basics 13 views 0 copies

Python code

31 lines
Python 3.7+
import argparse


def cmd_add(args):
    print(f"Adding {args.num1} + {args.num2} = {args.num1 + args.num2}")


def cmd_sub(args):
    print(f"Subtracting {args.num1} - {args.num2} = {args.num1 - args.num2}")


def main():
    parser = argparse.ArgumentParser(prog="calculator")
    subparsers = parser.add_subparsers(dest="command", required=True)

    parser_add = subparsers.add_parser("add", help="Add two numbers")
    parser_add.add_argument("num1", type=int)
    parser_add.add_argument("num2", type=int)
    parser_add.set_defaults(func=cmd_add)

    parser_sub = subparsers.add_parser("sub", help="Subtract two numbers")
    parser_sub.add_argument("num1", type=int)
    parser_sub.add_argument("num2", type=int)
    parser_sub.set_defaults(func=cmd_sub)

    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()

Output

stdout
python calculator.py add 10 5
Adding 10 + 5 = 15

python calculator.py sub 10 5
Subtracting 10 - 5 = 5

python calculator.py
usage: calculator [-h] {add,sub} ...
calculator: error: the following arguments are required: command

How it works

add_subparsers creates a dispatch point where the first positional argument selects which parser branch to use. Setting dest="command" stores the chosen subcommand name in args.command. The required=True flag forces a subcommand to be provided. Each subparser is an independent ArgumentParser with its own arguments and help text. set_defaults(func=...) attaches the handler function to the namespace, so after parse_args you can call args.func(args) to dispatch to the correct logic. This pattern keeps each command's argument parsing isolated while sharing a single entry point.

Common mistakes

  • Forgetting `required=True` on `add_subparsers`, which lets the program run silently without a command
  • Not attaching `set_defaults(func=...)` and trying to look up the handler with an if/elif on `args.command`
  • Reusing the same argument names across subparsers, which can cause shadowing or confusion if not scoped properly

Variations

  1. Add a third subcommand like 'mul' with its own parser and handler, following the same pattern
  2. Use `parser.set_defaults(func=main_help)` as a fallback for when no subcommand is given, if you want a default behavior

Real-world use cases

  • Building CLI tools like 'git add', 'git commit', and 'git push' where each verb is a separate subcommand with different flags.
  • Creating DevOps scripts with subcommands like 'deploy --env prod' and 'rollback --version 1.2' that run different deployment logic.
  • Implementing database migration tools with commands like 'migrate up' and 'migrate down', each accepting their own options.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Functions & basics

Related tutorials and quizzes for this topic.