杰瑞科技汇

Python subcommands 如何实现?

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.

Python subcommands 如何实现?-图1
(图片来源网络,侵删)

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:

Python subcommands 如何实现?-图2
(图片来源网络,侵删)
  • 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 mypy and 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 argparse method.
  • 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.
分享:
扫描分享到社交APP
上一篇
下一篇