Beginner · Lesson 46 of 94

Command-line interfaces with argparse

Watch this lesson

View full playlist ↗

Read the lesson

What you'll learn

Concept

A script that only works with values hard-coded at the top is a script you edit every time you run it. A command reads its inputs from the command line: organize photos/ --by year --dry-run. Python exposes the raw words in sys.argv, but parsing them by hand means writing help text, converting types, and reporting mistakes yourself. argparse does all of that from a declaration of what the command accepts.

Command-line interfaces with argparse: $ organize photos/ --by year --dry-run → ArgumentParser → args.folder = 'photos/'; args.by = 'year'; args.dry_run = True → usage text, error, exit code 2
Command-line interfaces with argparse · Figure 1 · Open for full size

You build an ArgumentParser, call add_argument once per input, then parse_args() returns an object with one attribute per argument. Positional arguments are required and identified by order. Options start with dashes, --by or -v, are optional unless you say otherwise, and become attributes with dashes turned to underscores. type=int converts and rejects non-numbers, default= fills in absent options, choices= restricts values, action="store_true" makes a flag that is False unless present, and action="append" collects a flag given several times into a list. help= on each argument builds the --help page.

add_argument(...) User types You get
"folder" photos/ args.folder == "photos/"
"--by", choices=["year", "type"], default="type" --by year args.by == "year"
"--limit", type=int, default=10 --limit 5 args.limit == 5
"--dry-run", action="store_true" --dry-run args.dry_run is True
"-t", "--tag", action="append" -t a -t b args.tag == ["a", "b"]

Structure matters as much as parsing. Put the parser in a function that builds and returns it, the real work in functions that take plain values, and a main(argv=None) that connects them, called under the main guard. The logic then never touches argparse, so it can be imported and tested with ordinary arguments, and main can be tested by passing a list of strings instead of relying on the real command line. Exit with sys.exit(1) or a parser.error(...) when something is wrong, so shells and CI can tell success from failure.

Command-line interfaces with argparse: main(argv) → build_parser() → parse_args(argv) → organize(folder, by, dry_run) — plain function, no argparse → return exit code
Command-line interfaces with argparse · Figure 2 · Open for full size

Code

A complete small command, saved as a file for the later blocks to use. The parser is built in one function, the work happens in another, and main takes an optional argument list so it can be driven from Python as well as the shell.

# organize.py
import argparse
import sys


def build_parser():
    parser = argparse.ArgumentParser(prog="organize", description="Sort files in a folder into subfolders.")
    parser.add_argument("folder", help="folder to organize")
    parser.add_argument("--by", choices=["year", "type"], default="type", help="grouping rule (default: type)")
    parser.add_argument("--limit", type=int, default=100, help="max files to move")
    parser.add_argument("--dry-run", action="store_true", help="show what would happen, move nothing")
    parser.add_argument("-t", "--tag", action="append", default=[], help="tag to add; repeatable")
    return parser


def organize(folder, by, limit, dry_run, tags):
    verb = "Would move" if dry_run else "Moving"
    return f"{verb} up to {limit} files in {folder} by {by}, tags={tags}"


def main(argv=None):
    args = build_parser().parse_args(argv)
    if args.limit <= 0:
        build_parser().error("--limit must be positive")
    print(organize(args.folder, args.by, args.limit, args.dry_run, args.tag))
    return 0


if __name__ == "__main__":
    sys.exit(main())

Driving the command from Python with argument lists, exactly as a test would. Defaults, conversion, a repeated flag, and a boolean switch all show up on the args object.

# drive.py
from organize import build_parser, main

args = build_parser().parse_args(["photos", "--by", "year", "-t", "trip", "-t", "2026", "--dry-run"])
print(args)

main(["photos"])
main(["docs", "--limit", "5", "--by", "year"])
Output
Namespace(folder='photos', by='year', limit=100, dry_run=True, tag=['trip', '2026'])
Moving up to 100 files in photos by type, tags=[]
Moving up to 5 files in docs by year, tags=[]

The help page is generated from the declarations. Running with --help prints it and exits with code 0.

# show_help.sh
python organize.py --help
Output
usage: organize [-h] [--by {year,type}] [--limit LIMIT] [--dry-run] [-t TAG]
                folder

Sort files in a folder into subfolders.

positional arguments:
  folder            folder to organize

options:
  -h, --help        show this help message and exit
  --by {year,type}  grouping rule (default: type)
  --limit LIMIT     max files to move
  --dry-run         show what would happen, move nothing
  -t, --tag TAG     tag to add; repeatable

Validation is free. A bad choice and a non-integer are rejected with a usage line and exit code 2, before any of your code runs.

# bad_input.sh
python organize.py photos --by size
echo "exit code: $?"
python organize.py photos --limit ten
Output
usage: organize [-h] [--by {year,type}] [--limit LIMIT] [--dry-run] [-t TAG]
                folder
organize: error: argument --by: invalid choice: 'size' (choose from 'year', 'type')
exit code: 2
usage: organize [-h] [--by {year,type}] [--limit LIMIT] [--dry-run] [-t TAG]
                folder
organize: error: argument --limit: invalid int value: 'ten'

Try it yourself

Create wordcount.py, a command that takes one or more file paths (nargs="+"), a --top N option with a default of 5, and a --ignore-case switch. It should print the most common words across all the files using Counter. Write two small text files, run the command with and without the flags, then run it with --help and with a missing file to see argparse's messages.

Common mistakes

What's next

Your scripts are real commands now. Next they will run other programs and manage files on disk, the automation half of scripting: subprocess, shutil, and os.

Course overview