🐍 Python Course · Stage 3 · Lesson 43 of 89 · argparse — CLI Arguments
Course Overview →

🔵 Read Beginner and Intermediate tabs. The Expert tab has the internals — worth reading once you finish Stage 3.

← subprocess — External Programs math — Math Functions →
thecodex.expert · The Codex Family of Knowledge
Python Standard Library

argparse — Command-Line Argument Parsing

argparse turns command-line arguments into a clean, validated Python interface — with automatic help text, type conversion, and error messages.

import argparse docs.python.org Last verified:
Canonical Definition

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.

🟩 Beginner

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.

⏱ 25 min📄 2 sections🔶 Prerequisite: Modules & Packages
The idea

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.

Pythongreet.py
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)
Shellterminal
$ 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 uppercase

Positional = 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

Pythonpositional_optional.py
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)
Dashes become 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

Continue to math →

🔵 Intermediate

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.

⏱ 30 min📄 2 sections🔶 Prerequisite: Beginner tab
💡

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.

Pythontypes_choices.py
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)

Pythonsubcommands.py
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 ""))
Commonly confused
action="store_true" for flags. A flag like --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= takes a callable, not a string. Use 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

Continue to math →

🔴 Expert

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.

⏱ 20 min📄 1 section🔶 Prerequisite: After Stage 3

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.

Pythonargparse_advanced.py
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

Continue to math →

Sources

1
Python Standard Library — argparse. docs.python.org/3/library/argparse.html.
2
Python HOWTO — Argparse Tutorial. docs.python.org/3/howto/argparse.html.
3
Python Standard Library — sys.argv. docs.python.org/3/library/sys.html#sys.argv.
Source confidence: High Last verified: Primary source: docs.python.org argparse