argparse
subparser
Python
command-line
programming

argparse identify which subparser was used

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

argparse subparsers let one command-line program expose multiple commands, such as tool build and tool test. Once you add subparsers, you usually need to know which one the user invoked so you can dispatch to the right handler. The cleanest solutions are to store the subcommand name with dest or to attach a handler function to each subparser with set_defaults().

Store the Subcommand Name with dest

The direct way to identify which subparser was used is to give add_subparsers() a destination attribute:

python
1import argparse
2
3parser = argparse.ArgumentParser(prog="mytool")
4subparsers = parser.add_subparsers(dest="command")
5
6build = subparsers.add_parser("build")
7build.add_argument("--target", default="release")
8
9test = subparsers.add_parser("test")
10test.add_argument("--verbose", action="store_true")
11
12args = parser.parse_args()
13print(args.command)

If the user runs mytool build, then args.command becomes "build". If the user runs mytool test, it becomes "test".

This is useful when you want to route execution with an if or match statement.

Dispatch Cleanly with set_defaults()

A more scalable pattern is to attach a handler function to each subparser:

python
1import argparse
2
3
4def handle_build(args):
5    print(f"Building target: {args.target}")
6
7
8def handle_test(args):
9    print(f"Running tests, verbose={args.verbose}")
10
11
12parser = argparse.ArgumentParser(prog="mytool")
13subparsers = parser.add_subparsers(dest="command", required=True)
14
15build = subparsers.add_parser("build")
16build.add_argument("--target", default="release")
17build.set_defaults(func=handle_build)
18
19test = subparsers.add_parser("test")
20test.add_argument("--verbose", action="store_true")
21test.set_defaults(func=handle_test)
22
23args = parser.parse_args()
24args.func(args)

This avoids a growing chain of if args.command == ... statements. Each subparser carries the code path it should invoke.

Use Both Patterns Together When Helpful

You do not have to choose only one pattern. It is often useful to keep both the command name and the handler:

python
1import argparse
2
3
4def deploy(args):
5    print(f"Deploying to {args.env}")
6
7
8parser = argparse.ArgumentParser(prog="deploytool")
9subparsers = parser.add_subparsers(dest="command", required=True)
10
11deploy_parser = subparsers.add_parser("deploy")
12deploy_parser.add_argument("--env", default="staging")
13deploy_parser.set_defaults(func=deploy)
14
15args = parser.parse_args()
16print(f"Executing command: {args.command}")
17args.func(args)

This is useful for logging, metrics, or debugging while still keeping dispatch logic clean.

Handle the Missing-Subcommand Case Properly

In modern Python, use required=True on add_subparsers() when a subcommand is mandatory. Without it, args.command may be None, and args.func may not exist.

If you need compatibility with older behavior, add a guard:

python
1args = parser.parse_args()
2
3if not hasattr(args, "func"):
4    parser.print_help()
5    parser.exit(2)
6
7args.func(args)

That makes the failure mode explicit instead of crashing with an attribute error.

Nested Subparsers Follow the Same Idea

For multi-level CLIs, the same approach still works. Each level can store its chosen name with dest, and the leaf subparser can set the final handler.

python
1import argparse
2
3parser = argparse.ArgumentParser(prog="cloud")
4services = parser.add_subparsers(dest="service", required=True)
5
6compute = services.add_parser("compute")
7actions = compute.add_subparsers(dest="action", required=True)
8
9create = actions.add_parser("create")
10create.add_argument("--name", required=True)
11create.set_defaults(func=lambda args: print(f"Creating {args.name}"))
12
13args = parser.parse_args()
14print(args.service, args.action)
15args.func(args)

The same principles apply. The command path is just deeper.

Which Pattern Should You Prefer

If the CLI is tiny, dest plus a simple conditional is fine. If the CLI is growing or has many subcommands, set_defaults(func=...) usually ages better.

A good rule of thumb is:

  • use dest when you mainly need the subcommand name
  • use set_defaults() when you mainly need command dispatch
  • combine both when you want clean dispatch and easy observability

Common Pitfalls

One common mistake is forgetting dest and then wondering why there is no args.command attribute. Without dest, the chosen subparser name is not stored automatically.

Another mistake is calling args.func(args) without ensuring a subcommand was actually selected. If no handler was attached, the program fails with an attribute error.

Developers also sometimes set defaults on the parent parser instead of on the individual subparsers, which can make dispatch behavior confusing.

Finally, if you are building a nested CLI, keep the names distinct. Reusing vague destination names across levels can make the parsed namespace harder to read.

Summary

  • Use add_subparsers(dest="command") to store the chosen subcommand name.
  • Use set_defaults(func=handler) on each subparser for clean dispatch.
  • 'required=True helps enforce that a subcommand is actually provided.'
  • You can combine dest and set_defaults() when you want both command names and handlers.
  • The same patterns work for nested subparsers too.

Course illustration
Course illustration

All Rights Reserved.