Learning Goals
3 minBy the end of this lesson you can:
- Build a parser with
argparse.ArgumentParser. - Add positional arguments (required, by order) and optional ones (
--flags). - Use
type=,default=,choices=, andaction="store_true". - Get a clean
--helpscreen and friendly errors without writing them yourself.
Warm-Up · The Pain of Hand-Parsing
5 minImagine supporting all of this with raw sys.argv:
python backup.py /data --output /backups --compress --verbose python backup.py /data -o /backups -v python backup.py --help
You'd need to scan the list, match flags, handle both --output and -o, deal with missing values, and write your own help text. That's dozens of fiddly, bug-prone lines.
argparse is in the standard library and does all of that for you. You describe the arguments you want; it parses, validates, converts, and documents them. Describe, don't parse.
New Concept · Describing Arguments
14 minThe four-line skeleton
import argparse parser = argparse.ArgumentParser(description="Greet someone.") parser.add_argument("name") # positional: required args = parser.parse_args() # reads sys.argv for you print(f"Hello, {args.name}!")
Run python greet.py Aisha → Hello, Aisha!. Run it with no name and argparse prints a usage error and exits non-zero — automatically.
Positional vs. optional
- Positional —
add_argument("name"). Required, identified by position, no dashes. - Optional —
add_argument("--times"). Starts with--, can be left out, has a default.
parser.add_argument("name") parser.add_argument("--times", type=int, default=1) args = parser.parse_args() for _ in range(args.times): print(f"Hello, {args.name}!")
$ python greet.py Aisha --times 3 Hello, Aisha! Hello, Aisha! Hello, Aisha!
Note type=int — argparse converts "3" to 3 for you, and rejects --times abc with a clear error.
Short flags and boolean switches
parser.add_argument("-n", "--times", type=int, default=1) parser.add_argument("-v", "--verbose", action="store_true") args = parser.parse_args() if args.verbose: print("verbose mode on")
-nand--timesare the same argument — short and long form.action="store_true"makes a flag that'sTruewhen present,Falsewhen absent — no value needed.
Restricting choices
parser.add_argument("--level", choices=["debug", "info", "warning"], default="info")
If the user types --level shout, argparse rejects it and lists the valid options. You never write that check yourself.
The free help screen
Add help= to each argument and -h/--help works automatically:
parser.add_argument("name", help="who to greet") parser.add_argument("-n", "--times", type=int, default=1, help="how many times (default 1)")
$ python greet.py --help
usage: greet.py [-h] [-n TIMES] name
Greet someone.
positional arguments:
name who to greet
options:
-h, --help show this help message and exit
-n TIMES, --times TIMES
how many times (default 1)Worked Example · A File Search Tool
12 minGoal: python findtext.py report.txt error --ignore-case --count — search a file for a word, optionally case-insensitive, optionally just count matches.
import argparse parser = argparse.ArgumentParser(description="Find a word in a file.") parser.add_argument("file", help="file to search") parser.add_argument("word", help="word to look for") parser.add_argument("-i", "--ignore-case", action="store_true", help="case-insensitive search") parser.add_argument("-c", "--count", action="store_true", help="print only the number of matches") args = parser.parse_args() lines = open(args.file, encoding="utf-8").read().splitlines() needle = args.word.lower() if args.ignore_case else args.word matches = [] for n, line in enumerate(lines, start=1): hay = line.lower() if args.ignore_case else line if needle in hay: matches.append((n, line)) if args.count: print(len(matches)) else: for n, line in matches: print(f"{n}: {line}")
$ python findtext.py log.txt ERROR --count 3 $ python findtext.py log.txt error -i 12: [error] disk almost full 47: [error] retry failed
Read the code
Notice --ignore-case becomes args.ignore_case — argparse turns dashes into underscores so it's a valid attribute name. Two boolean switches and two positionals gave us a flexible tool, with help text and validation thrown in. This is the standard shape of a real CLI: positionals for the what, flags for the how.
Try It Yourself
13 minRewrite Lesson 2's greeter using argparse: a positional name and an optional --times (default 1). Run --help and admire your free documentation.
Take a positional temp (a float) and an optional --to with choices=["C", "F"] defaulting to "F". Convert and print.
Hint
import argparse p = argparse.ArgumentParser() p.add_argument("temp", type=float) p.add_argument("--to", choices=["C", "F"], default="F") a = p.parse_args() if a.to == "F": print(a.temp * 9/5 + 32) else: print((a.temp - 32) * 5/9)
Build roll.py: optional --sides (default 6), --count (default 1), and a --total switch that prints the sum instead of each roll. Use random.randint.
Hint
import argparse, random p = argparse.ArgumentParser(description="Roll dice.") p.add_argument("--sides", type=int, default=6) p.add_argument("--count", type=int, default=1) p.add_argument("--total", action="store_true") a = p.parse_args() rolls = [random.randint(1, a.sides) for _ in range(a.count)] print(sum(rolls) if a.total else rolls)
Mini-Challenge · A Real grep Clone
8 minExtend the search tool: add --line-numbers (off by default), --invert (show lines that do not match), and accept the file as an optional argument that defaults to reading sys.stdin if omitted. Make sure --help reads cleanly.
Show the key additions
import argparse, sys p = argparse.ArgumentParser(description="grep clone") p.add_argument("word") p.add_argument("file", nargs="?") # ? = optional positional p.add_argument("-n", "--line-numbers", action="store_true") p.add_argument("-v", "--invert", action="store_true") a = p.parse_args() source = open(a.file, encoding="utf-8") if a.file else sys.stdin for i, line in enumerate(source, start=1): hit = a.word in line if hit != a.invert: # XOR: invert flips the test prefix = f"{i}: " if a.line_numbers else "" print(prefix + line.rstrip())
nargs="?" makes a positional optional; hit != a.invert is a tidy way to flip the match when --invert is set.
Recap
3 minargparse lets you describe arguments instead of parsing them. Positionals (no dashes) are required and ordered; optionals (--flag) have defaults. Use type= to convert, default= to fall back, choices= to restrict, and action="store_true" for boolean switches. In return you get validation, friendly errors, dash-to-underscore attribute names, and a polished --help screen — none of which you have to write. Next lesson: subcommands, for git-style tools.
Vocabulary Card
- positional argument
- A required value identified by its position, no dashes.
- optional argument
- A
--flagthat can be left out and has a default. - store_true
- An action making a flag
Truewhen present,Falseotherwise. - nargs
- Controls how many values an argument accepts (
"?"= optional,"+"= one or more).
Homework
4 minBuild resize.py (just the argument layer — no real image work yet): a positional image, an optional --width and --height (both ints), a --keep-aspect switch, and --format with choices=["jpg", "png", "webp"]. Print back the parsed plan as a sentence. Run --help and confirm it reads like a real tool.
Sample · resize.py argument layer
import argparse p = argparse.ArgumentParser(description="Resize an image.") p.add_argument("image", help="path to the image") p.add_argument("--width", type=int, help="target width in px") p.add_argument("--height", type=int, help="target height in px") p.add_argument("--keep-aspect", action="store_true", help="preserve aspect ratio") p.add_argument("--format", choices=["jpg", "png", "webp"], default="jpg", help="output format") a = p.parse_args() print(f"Would resize {a.image} to {a.width}x{a.height} " f"as {a.format} (keep aspect: {a.keep_aspect})")
Non-negotiables: one positional, typed optionals, a store_true switch, a choices flag, and a clean --help.