Every script eventually needs arguments. Here's how to handle them properly.
Basic Usage
import argparse
parser = argparse.ArgumentParser(description="Process some files")
parser.add_argument("filename", help="File to process")
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
args = parser.parse_args()
print(f"Processing {args.filename}")
if args.verbose:
print("Verbose mode enabled")Usage:
python script.py data.txt
python script.py data.txt --verbose
python script.py --helpPositional vs Optional Arguments
# Positional - required, no dash
parser.add_argument("input", help="Input file")
parser.add_argument("output", help="Output file")
# Optional - has dash, not required by default
parser.add_argument("-n", "--number", type=int, help="Number of items")
parser.add_argument("--config", help="Config file path")Usage:
python script.py input.txt output.txt
python script.py input.txt output.txt -n 10 --config prod.yamlArgument Types
# String (default)
parser.add_argument("--name", type=str)
# Integer
parser.add_argument("-n", "--count", type=int)
# Float
parser.add_argument("--threshold", type=float)
# File
parser.add_argument("--input", type=argparse.FileType("r"))
parser.add_argument("--output", type=argparse.FileType("w"))Custom type:
def positive_int(value):
ivalue = int(value)
if ivalue <= 0:
raise argparse.ArgumentTypeError(f"{value} is not a positive integer")
return ivalue
parser.add_argument("--count", type=positive_int)Default Values
parser.add_argument("--port", type=int, default=8080)
parser.add_argument("--host", default="localhost")
parser.add_argument("--debug", action="store_true") # Default is FalseRequired Optional Arguments
parser.add_argument("--api-key", required=True, help="API key (required)")Choices
parser.add_argument(
"--format",
choices=["json", "csv", "xml"],
default="json",
help="Output format"
)
parser.add_argument(
"--level",
type=int,
choices=range(1, 6),
help="Level 1-5"
)Boolean Flags
# Flag - presence means True
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("-q", "--quiet", action="store_true")
# Store False when present
parser.add_argument("--no-cache", action="store_false", dest="cache")Multiple Values
# Fixed number
parser.add_argument("--point", nargs=2, type=float, metavar=("X", "Y"))
# Zero or more
parser.add_argument("--tags", nargs="*")
# One or more
parser.add_argument("files", nargs="+")
# Optional positional
parser.add_argument("output", nargs="?", default="out.txt")Subcommands
For git-style CLIs:
parser = argparse.ArgumentParser(prog="myapp")
subparsers = parser.add_subparsers(dest="command", required=True)
# 'init' command
init_parser = subparsers.add_parser("init", help="Initialize project")
init_parser.add_argument("--template", default="basic")
# 'run' command
run_parser = subparsers.add_parser("run", help="Run the project")
run_parser.add_argument("--port", type=int, default=8000)
args = parser.parse_args()
if args.command == "init":
print(f"Initializing with template: {args.template}")
elif args.command == "run":
print(f"Running on port {args.port}")Usage:
myapp init --template advanced
myapp run --port 3000Help Text
parser = argparse.ArgumentParser(
prog="myapp",
description="My awesome application",
epilog="Example: myapp --config prod.yaml input.txt"
)
parser.add_argument(
"-c", "--config",
metavar="FILE",
help="Configuration file (default: %(default)s)",
default="config.yaml"
)Generated help:
usage: myapp [-h] [-c FILE] ...
My awesome application
options:
-h, --help show this help message and exit
-c FILE, --config FILE
Configuration file (default: config.yaml)
Example: myapp --config prod.yaml input.txt
Argument Groups
parser = argparse.ArgumentParser()
# Group related arguments
server_group = parser.add_argument_group("Server options")
server_group.add_argument("--host", default="localhost")
server_group.add_argument("--port", type=int, default=8080)
auth_group = parser.add_argument_group("Authentication")
auth_group.add_argument("--username")
auth_group.add_argument("--password")Mutually Exclusive
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
# Can't use both --verbose and --quietComplete Example
#!/usr/bin/env python3
import argparse
import sys
def main():
parser = argparse.ArgumentParser(
description="Process data files",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s input.csv
%(prog)s input.csv -o output.json --format json
%(prog)s input.csv --verbose --dry-run
"""
)
parser.add_argument("input", help="Input file")
parser.add_argument("-o", "--output", help="Output file")
parser.add_argument(
"-f", "--format",
choices=["csv", "json", "xml"],
default="csv",
help="Output format (default: %(default)s)"
)
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
if args.verbose:
print(f"Processing {args.input}")
# Do the work...
return 0
if __name__ == "__main__":
sys.exit(main())Alternatives
Click - decorator-based, more Pythonic:
import click
@click.command()
@click.argument("filename")
@click.option("--verbose", is_flag=True)
def cli(filename, verbose):
click.echo(f"Processing {filename}")Typer - Click + type hints:
import typer
def main(filename: str, verbose: bool = False):
print(f"Processing {filename}")
typer.run(main)Use argparse for simple scripts, Click/Typer for complex CLIs.
React to this post: