Of course! In Python, "subcommands" are a powerful way to structure a command-line application, especially for tools that can perform several distinct, related tasks. The most famous example is git: git status, git commit, git push, etc. Here, status, commit, and push are subcommands of the main git command.

Let's break down how to implement subcommands in Python, from the simplest methods to the most robust and modern approach.
The Core Concept
A command-line application with subcommands generally works like this:
<program_name> <subcommand> [subcommand_options] [subcommand_arguments]
<program_name>: The main entry point for your application (e.g.,my_tool).<subcommand>: The specific action to perform (e.g.,create,delete,list).[subcommand_options]: Flags specific to that subcommand (e.g.,--name).[subcommand_arguments]: Positional arguments for that subcommand (e.g., a file name).
Method 1: The Manual argparse Approach (The Classic)
This is the foundational way to do it using Python's built-in argparse library. You create a main parser and then add subparsers to it, each with its own set of arguments.
Pros:

- Built-in, no external dependencies.
- Very flexible and explicit.
Cons:
- Can become verbose and repetitive for complex applications.
- You have to manually wire up the functions to run for each subcommand.
Example: A simple to-do list tool
Let's create a tool with add, list, and done subcommands.
# todo_app.py
import argparse
def add_item(args):
"""Function to handle the 'add' subcommand."""
print(f"Adding item: '{args.item}'")
# In a real app, you would add this to a database or file.
# e.g., todos.append(args.item)
def list_items(args):
"""Function to handle the 'list' subcommand."""
print("Listing all items:")
# In a real app, you would fetch and print all items.
# e.g., for item in todos: print(f"- {item}")
def mark_done(args):
"""Function to handle the 'done' subcommand."""
print(f"Marking item as done: '{args.item}'")
# In a real app, you would find and update the item.
def main():
# 1. Create the top-level parser
parser = argparse.ArgumentParser(description="A simple to-do list application.")
subparsers = parser.add_subparsers(dest="command", help="Available sub-commands", required=True)
# 2. Create the parser for the "add" command
parser_add = subparsers.add_parser("add", help="Add a new to-do item.")
parser_add.add_argument("item", type=str, help="The to-do item to add.")
parser_add.set_defaults(func=add_item)
# 3. Create the parser for the "list" command
parser_list = subparsers.add_parser("list", help="List all to-do items.")
# No arguments needed for list, but we still need a function
parser_list.set_defaults(func=list_items)
# 4. Create the parser for the "done" command
parser_done = subparsers.add_parser("done", help="Mark an item as done.")
parser_done.add_argument("item", type=str, help="The item to mark as done.")
parser_done.set_defaults(func=mark_done)
# 5. Parse the arguments and call the correct function
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()
How to run it:
# Add an item $ python todo_app.py add "Buy groceries" Adding item: 'Buy groceries' # List items $ python todo_app.py list Listing all items: # Mark an item as done $ python todo_app.py done "Buy groceries" Marking item as done: 'Buy groceries' # Get help for a specific subcommand $ python todo_app.py add --help usage: todo_app.py add [-h] item positional arguments: item The to-do item to add. options: -h, --help show this help message and exit
Method 2: Using a Third-Party Library (Recommended)
For any non-trivial application, using a library is highly recommended. They simplify the process, reduce boilerplate, and often provide extra features like auto-completion (argcomplete), better help text, and type hints.
Here are two of the most popular choices:
A) click (The Modern Favorite)
click is a powerful and composable command-line interface creation toolkit. It's known for its excellent documentation, clean API, and great support for type hints.
Installation:
pip install click
Example: The same to-do tool with click
# todo_app_click.py
import click
@click.group()
def cli():
"""A simple to-do list application."""
pass
@cli.command()
@click.argument('item')
def add(item):
"""Adds a new to-do item."""
click.echo(f"Adding item: '{item}'")
@cli.command()
def list():
"""Lists all to-do items."""
click.echo("Listing all items:")
@cli.command()
@click.argument('item')
def done(item):
"""Marks an item as done."""
click.echo(f"Marking item as done: '{item}'")
if __name__ == '__main__':
cli()
How to run it:
# The command structure is very clean $ python todo_app_click.py add "Buy groceries" Adding item: 'Buy groceries' # Get help $ python todo_app_click.py --help Usage: todo_app_click.py [OPTIONS] COMMAND [ARGS]... A simple to-do list application. Options: --help Show this message and exit. Commands: add Adds a new to-do item. done Marks an item as done. list Lists all to-do items. # Get help for a subcommand $ python todo_app_click.py add --help Usage: todo_app_click.py add [OPTIONS] ITEM Adds a new to-do item. Args: ITEM [required]
Why click is often preferred:
- Decorator-based: The code is very clean and declarative.
- Excellent types: Works seamlessly with
mypyand type hints. - Rich features: Built-in support for prompts, progress bars, colors, and more.
- Less boilerplate: No need to manually define a function and link it with
set_defaults.
B) typer (The Type-Hints First Library)
typer is a library built on top of click that is designed to be 100% type-checkable. If you love Python type hints, typer is a fantastic choice. It generates click code under the hood.
Installation:
pip install "typer[all]"
Example: The same to-do tool with typer
# todo_app_typer.py
import typer
from typing import Optional
app = typer.Typer()
@app.command()
def add(item: str):
"""Adds a new to-do item."""
typer.echo(f"Adding item: '{item}'")
@app.command()
def list():
"""Lists all to-do items."""
typer.echo("Listing all items:")
@app.command()
def done(item: str):
"""Marks an item as done."""
typer.echo(f"Marking item as done: '{item}'")
if __name__ == "__main__":
app()
The code looks almost identical to the click version, but it's designed from the ground up to be type-hint driven.
Summary and Recommendation
| Feature | argparse (Manual) |
click |
typer |
|---|---|---|---|
| Dependencies | None (built-in) | click |
typer (builds on click) |
| Ease of Use | Good for simple cases, verbose for complex ones. | Excellent. Very intuitive. | Excellent. Extremely intuitive if you use type hints. |
| Code Style | Imperative (define parser, then call function). | Declarative (use decorators). | Declarative (use decorators with type hints). |
| Type Hints | Not integrated. | Good support. | Excellent support. Core feature. |
| Best For | Quick scripts, avoiding dependencies, learning the fundamentals. | General-purpose, robust CLI tools. The industry standard. | Projects where type safety and developer experience are top priorities. |
Final Recommendation:
- For a quick script or if you can't add dependencies, use the manual
argparsemethod. - For almost any new project, start with
click. It's the most popular, well-supported, and provides a great balance of power and simplicity. - If your team loves type hints and you want the best possible static analysis for your CLI code, choose
typer.
