argparse is the standard library module for parsing command-line arguments. You declare the arguments your program accepts; argparse parses sys.argv, converts and validates values, generates --help output, and reports usage errors automatically.
Build a command-line interface
What you will learn: how to create a parser, add positional and optional arguments, and get automatic --help and error messages for free.
How to read this tab: Save each example as a .py file and run it from a real terminal with different arguments — that is the only way argparse clicks.
When you run a program like python backup.py /data --verbose, argparse is what reads /data and --verbose and hands them to your code as clean Python values — while writing the help screen and error messages for you.
Three steps, always the same: create ArgumentParser, add_argument for each input, parse_args(). argparse then handles --help, type errors, and missing-argument errors automatically — you never write that code.
Your first parser
Three steps: create a parser, add the arguments you accept, then parse. argparse handles the rest — including -h/--help automatically.
import argparse
parser = argparse.ArgumentParser(description="Greet a user by name.")
parser.add_argument("name", help="the name to greet")
parser.add_argument("--shout", action="store_true", help="use uppercase")
args = parser.parse_args()
greeting = f"Hello, {args.name}!"
if args.shout:
greeting = greeting.upper()
print(greeting)$ python greet.py Priya
Hello, Priya!
$ python greet.py Priya --shout
HELLO, PRIYA!
$ python greet.py # missing required argument
usage: greet.py [-h] [--shout] name
greet.py: error: the following arguments are required: name
$ python greet.py --help # generated automatically
usage: greet.py [-h] [--shout] name
Greet a user by name.
positional arguments:
name the name to greet
options:
-h, --help show this help message and exit
--shout use uppercasePositional = required and identified by position; optional = identified by name and not required. add_argument("name") is positional. add_argument("--name") is optional. Dashes become underscores in the result: --dry-run reads as args.dry_run.
Positional vs optional arguments
import argparse
parser = argparse.ArgumentParser()
# Positional — required, identified by position
parser.add_argument("source")
parser.add_argument("destination")
# Optional — identified by name (starts with - or --), not required by default
parser.add_argument("-v", "--verbose", action="store_true")
parser.add_argument("--retries", default=3) # default if not given
args = parser.parse_args(["in.txt", "out.txt", "--verbose"])
print(args.source) # in.txt
print(args.destination) # out.txt
print(args.verbose) # True
print(args.retries) # 3 (the default)
# Note: --retries becomes args.retries (dashes -> underscores)An option named --dry-run is accessed as args.dry_run. argparse converts dashes to underscores so the attribute name is a valid Python identifier.
✅ Beginner tab complete
- I can create an ArgumentParser and add an argument
- I know positional args are required; optional args (--name) are not by default
- I know action="store_true" makes a flag
- I know dashes in --dry-run become args.dry_run
Types, choices, nargs, subcommands
What you will learn: type conversion and validation, restricting values with choices, collecting lists with nargs, and building git-style subcommands.
How to read this tab: Try giving deliberately wrong input (--count abc) to see argparse generate the error message for you.
Let argparse validate for you. type=int converts and rejects bad input with a clean message. choices=[...] restricts allowed values. nargs="+" collects a list. You write the declaration; argparse does the checking.
Type conversion, choices, and nargs
argparse can convert and validate values for you: convert to int or float, restrict to a set of choices, or collect multiple values with nargs.
import argparse
parser = argparse.ArgumentParser()
# type= converts and validates — bad input gives a clean error
parser.add_argument("--count", type=int, default=1)
parser.add_argument("--ratio", type=float)
# choices= restricts allowed values
parser.add_argument("--mode", choices=["fast", "safe", "debug"], default="safe")
# nargs collects multiple values into a list
parser.add_argument("--files", nargs="+") # one or more -> list
parser.add_argument("--coords", nargs=2, type=float) # exactly two
# required=True forces an optional argument to be given
parser.add_argument("--output", required=True)
args = parser.parse_args(
["--count", "5", "--mode", "fast", "--files", "a.txt", "b.txt",
"--output", "result.txt"])
print(args.count) # 5 (an int, not "5")
print(args.mode) # fast
print(args.files) # ['a.txt', 'b.txt']
# Invalid type gives an automatic error:
# $ prog --count abc
# error: argument --count: invalid int value: 'abc'
# Invalid choice:
# $ prog --mode turbo
# error: argument --mode: invalid choice: 'turbo' (choose from 'fast', 'safe', 'debug')Subcommands build tools like git. add_subparsers() lets one program have multiple commands, each with its own arguments. Store the chosen command with dest="command" and dispatch on it.
Subcommands (like git commit, git push)
import argparse
parser = argparse.ArgumentParser(prog="tool")
subparsers = parser.add_subparsers(dest="command", required=True)
# "tool add <item>"
add = subparsers.add_parser("add", help="add an item")
add.add_argument("item")
# "tool remove <item> --force"
remove = subparsers.add_parser("remove", help="remove an item")
remove.add_argument("item")
remove.add_argument("--force", action="store_true")
args = parser.parse_args(["add", "milk"])
print(args.command) # add
print(args.item) # milk
# Dispatch on the chosen subcommand
if args.command == "add":
print(f"Adding {args.item}")
elif args.command == "remove":
print(f"Removing {args.item}" + (" (forced)" if args.force else ""))--verbose that is either present or absent uses action="store_true" — it becomes True when given, False otherwise. Without it, argparse expects a value after the flag.type=int, not type="int". Any one-argument callable works, including your own functions — type=Path or type=custom_validator.✅ Intermediate tab complete
- I can use type=int to convert and validate
- I can restrict values with choices=[...]
- I can collect multiple values with nargs
- I can build subcommands with add_subparsers
Custom types, groups, parser composition
What you will learn: custom validation callables for type=, mutually exclusive groups, parent parsers, and the set_defaults(func=...) dispatch pattern.
How to read this tab: Read when building a multi-command CLI tool — the parent-parser + set_defaults pattern scales cleanly.
Custom types, argument groups, and parser composition
Because type= accepts any callable that takes a string and returns a value (raising ValueError or argparse.ArgumentTypeError on failure), you can plug in arbitrary validation and conversion. Mutually exclusive groups enforce that only one of a set of options is given, and parent parsers let you share common arguments across multiple subcommands.
import argparse
from pathlib import Path
# Custom type — a callable str -> value
def existing_file(s):
p = Path(s)
if not p.is_file():
raise argparse.ArgumentTypeError(f"{s} is not a file")
return p
parser = argparse.ArgumentParser()
parser.add_argument("--config", type=existing_file) # validated Path
# Mutually exclusive group — at most one may be given
group = parser.add_mutually_exclusive_group()
group.add_argument("--verbose", action="store_true")
group.add_argument("--quiet", action="store_true")
# Giving BOTH --verbose and --quiet is an automatic error
# Parent parser — share common args across subcommands
common = argparse.ArgumentParser(add_help=False)
common.add_argument("--log-level", default="INFO")
main = argparse.ArgumentParser()
subs = main.add_subparsers(dest="cmd")
subs.add_parser("build", parents=[common]) # inherits --log-level
subs.add_parser("deploy", parents=[common]) # inherits --log-level
# Built-in actions beyond store_true:
parser.add_argument("--tag", action="append") # repeatable -> list
parser.add_argument("-v", action="count", default=0) # -vvv -> 3
parser.add_argument("--version", action="version", version="1.0")For very large CLIs, the standard pattern is to attach a handler function to each subparser with subparser.set_defaults(func=handler), then call args.func(args) after parsing — a clean dispatch mechanism that keeps each command's logic in its own function. While third-party libraries like Click and Typer offer more concise decorator-based APIs, argparse is in the standard library, has zero dependencies, and is sufficient for the large majority of command-line tools.
✅ Expert tab complete
- I can write a custom type= callable that validates input
- I can use a mutually exclusive group
- I can share arguments via a parent parser
- I know the set_defaults(func=handler) dispatch pattern