Of course! The term "subcommand" in Python refers to a command-line interface (CLI) pattern where a main program has multiple sub-commands, each performing a different action. This is the same pattern used by powerful tools like git (git commit, git push), docker (docker run, docker build), and pip (pip install, pip list).

Python has two excellent, built-in ways to handle subcommands: the older argparse module and the newer, more modern click library.
The Standard Library: argparse
argparse is the built-in, powerful, and flexible way to create command-line interfaces in Python. It's part of the standard library, so no installation is needed.
How argparse Handles Subcommands
argparse handles subcommands by creating a "subparser" for each one. You define a main parser and then add subparsers to it. Each subparser can have its own set of arguments.
Example: A Simple Task Manager
Let's create a CLI tool called task with three subcommands:

task add <description>: Adds a new task.task list: Lists all tasks.task complete <task_id>: Marks a task as complete.
File: task_cli.py
import argparse
import json
import os
# A simple file to store our tasks
TASKS_FILE = "tasks.json"
def load_tasks():
"""Loads tasks from the JSON file."""
if not os.path.exists(TASKS_FILE):
return []
try:
with open(TASKS_FILE, 'r') as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return []
def save_tasks(tasks):
"""Saves tasks to the JSON file."""
with open(TASKS_FILE, 'w') as f:
json.dump(tasks, f, indent=4)
# --- Subcommand Functions ---
def add_task(args):
"""Function to handle the 'add' subcommand."""
tasks = load_tasks()
new_task = {
"id": len(tasks) + 1,
"description": args.description,
"completed": False
}
tasks.append(new_task)
save_tasks(tasks)
print(f"Added task: '{args.description}' (ID: {new_task['id']})")
def list_tasks(args):
"""Function to handle the 'list' subcommand."""
tasks = load_tasks()
if not tasks:
print("No tasks found.")
return
print("--- Your Tasks ---")
for task in tasks:
status = "[✓]" if task['completed'] else "[ ]"
print(f"{status} Task ID: {task['id']} - {task['description']}")
print("------------------")
def complete_task(args):
"""Function to handle the 'complete' subcommand."""
tasks = load_tasks()
task_id_to_complete = args.task_id
found = False
for task in tasks:
if task['id'] == task_id_to_complete:
task['completed'] = True
found = True
print(f"Completed task ID: {task_id_to_complete}")
break
if not found:
print(f"Error: Task with ID {task_id_to_complete} not found.")
save_tasks(tasks)
# --- Main Argument Parser Setup ---
def main():
# 1. Create the main parser
parser = argparse.ArgumentParser(description="A simple command-line task manager.")
# 2. Create the subparsers object
subparsers = parser.add_subparsers(dest="command", help="Available sub-commands", required=True)
# 3. Create a parser for the 'add' subcommand
parser_add = subparsers.add_parser("add", help="Add a new task.")
parser_add.add_argument("description", type=str, help="The description of the task.")
parser_add.set_defaults(func=add_task) # Link to the handler function
# 4. Create a parser for the 'list' subcommand
parser_list = subparsers.add_parser("list", help="List all tasks.")
parser_list.set_defaults(func=list_task) # Link to the handler function
# 5. Create a parser for the 'complete' subcommand
parser_complete = subparsers.add_parser("complete", help="Mark a task as complete.")
parser_complete.add_argument("task_id", type=int, help="The ID of the task to complete.")
parser_complete.set_defaults(func=complete_task) # Link to the handler function
# 6. Parse the arguments and call the correct function
args = parser.parse_args()
args.func(args) # Call the function associated with the subcommand
if __name__ == "__main__":
main()
How to Run It
-
Save the code as
task_cli.py. -
Run it from your terminal:
-
Get help (shows subcommands):
python task_cli.py --help
Output:
usage: task_cli.py [-h] {add,list,complete} ... A simple command-line task manager. positional arguments: {add,list,complete} Available sub-commands add Add a new task. list List all tasks. complete Mark a task as complete. options: -h, --help show this help message and exit -
Add a task:
python task_cli.py add "Buy groceries" python task_cli.py add "Finish the report"
-
List tasks:
python task_cli.py list
Output:
--- Your Tasks --- [ ] Task ID: 1 - Buy groceries [ ] Task ID: 2 - Finish the report ------------------ -
Complete a task:
python task_cli.py complete 1
-
List tasks again (notice the change):
python task_cli.py list
Output:
--- Your Tasks --- [✓] Task ID: 1 - Buy groceries [ ] Task ID: 2 - Finish the report ------------------
-
The Modern Alternative: click
click is a third-party library that is extremely popular for creating CLIs. It's known for its clean syntax, excellent documentation, and powerful features like type conversion, validation, and nested help.
First, you need to install it:
pip install click
How click Handles Subcommands
click uses decorators to define commands and subcommands. The @click.group() decorator creates the main command, and @cli.command() defines a subcommand within that group.
Example: The Same Task Manager with click
File: task_cli_click.py
import click
import json
import os
# A simple file to store our tasks
TASKS_FILE = "tasks_click.json"
def load_tasks():
if not os.path.exists(TASKS_FILE):
return []
try:
with open(TASKS_FILE, 'r') as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return []
def save_tasks(tasks):
with open(TASKS_FILE, 'w') as f:
json.dump(tasks, f, indent=4)
# 1. Create a group, which will be our main entry point
@click.group()
def cli():
"""A simple command-line task manager using Click."""
pass
# 2. Add a command to the group
@cli.command()
@click.argument("description")
def add(description):
"""Adds a new task."""
tasks = load_tasks()
new_task = {
"id": len(tasks) + 1,
"description": description,
"completed": False
}
tasks.append(new_task)
save_tasks(tasks)
click.echo(f"Added task: '{description}' (ID: {new_task['id']})")
# 3. Add another command
@cli.command()
def list():
"""Lists all tasks."""
tasks = load_tasks()
if not tasks:
click.echo("No tasks found.")
return
click.echo("--- Your Tasks ---")
for task in tasks:
status = "[✓]" if task['completed'] else "[ ]"
click.echo(f"{status} Task ID: {task['id']} - {task['description']}")
click.echo("------------------")
# 4. Add a command with an option
@cli.command()
@click.argument("task_id", type=int)
def complete(task_id):
"""Marks a task as complete."""
tasks = load_tasks()
found = False
for task in tasks:
if task['id'] == task_id:
task['completed'] = True
found = True
click.echo(f"Completed task ID: {task_id}")
break
if not found:
click.echo(f"Error: Task with ID {task_id} not found.", err=True)
save_tasks(tasks)
# This ensures the script can be run directly
if __name__ == '__main__':
cli()
How to Run It
The usage is very similar but often feels more intuitive.
-
Save the code as
task_cli_click.py. -
Run it from your terminal:
-
Get help (shows subcommands):
python task_cli_click.py --help
Output:
Usage: task_cli_click.py [OPTIONS] COMMAND [ARGS]... A simple command-line task manager using Click. Options: --help Show this message and exit. Commands: add Adds a new task. complete Marks a task as complete. list Lists all tasks. -
Add a task:
python task_cli_click.py add "Buy groceries" python task_cli_click.py add "Finish the report"
-
List tasks:
python task_cli_click.py list
-
Complete a task:
python task_cli_click.py complete 1
-
Comparison: argparse vs. click
| Feature | argparse |
click |
|---|---|---|
| Availability | Built-in (no installation needed) | Requires pip install click |
| Syntax | Procedural (create parser, add arguments, parse) | Declarative (using decorators) |
| Readability | Can get verbose with complex CLIs. | Generally considered cleaner and more Pythonic. |
| Features | Very powerful, but some features require extra code. | Batteries-included: type conversion, prompts, colors, etc. |
| Help Output | Standard, functional help messages. | Highly customizable and attractive help by default. |
| Type Handling | Manual type checking or using type argument. |
Automatic type conversion and validation. |
| Best For | Simple to moderately complex scripts, quick tools, environments where you can't install packages. | Professional, robust, and user-friendly CLIs. |
Which One Should You Choose?
- For a quick script or a simple tool: Use
argparse. It's there, it works, and it's perfectly capable. - For a serious project, a library, or a tool that will be used by many people: Use
click. It will save you time in the long run and result in a much better user experience. The declarative style is also easier to read and maintain as your CLI grows in complexity.
