Python argparse & sys.argv — CLI Arguments
When you run a script from the terminal, the words after the script name are command-line arguments. Python gives you two ways to read them: the raw sys.argv list, and the full-featured argparse library.
# $ python greet.py Ada --times 3
import sys
print(sys.argv) # ['greet.py', 'Ada', '--times', '3']sys.argv — the raw list
Section titled “sys.argv — the raw list”sys.argv is a list of strings. The first item is always the script name; the rest are the arguments, as strings.
import sys
# $ python add.py 3 4
if len(sys.argv) != 3:
print("Usage: python add.py NUM NUM")
sys.exit(1)
a = int(sys.argv[1]) # convert from string!
b = int(sys.argv[2])
print(a + b) # 7sys.argv is fine for one or two arguments, but it forces you to handle conversion, validation, defaults, and help text by hand. For anything real, use argparse.
argparse — the proper way
Section titled “argparse — the proper way”argparse parses arguments, converts types, generates --help, and reports errors automatically.
import argparse
parser = argparse.ArgumentParser(description="Greet someone.")
parser.add_argument("name", help="who to greet")
parser.add_argument("--times", type=int, default=1, help="how many times")
args = parser.parse_args() # reads from sys.argv
for _ in range(args.times):
print(f"Hello, {args.name}!")Running it:
$ python greet.py Ada --times 2
Hello, Ada!
Hello, Ada!
$ python greet.py --help
usage: greet.py [-h] [--times TIMES] name
...Positional vs optional arguments
Section titled “Positional vs optional arguments”| Kind | Defined as | Example |
|---|---|---|
| Positional | add_argument("name") | python app.py Ada |
| Optional | add_argument("--times") | python app.py --times 3 |
| Flag | add_argument("--verbose", action="store_true") | python app.py --verbose |
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("filename") # required positional
parser.add_argument("--limit", type=int, default=10) # optional with default
parser.add_argument("--verbose", action="store_true")# flag -> True/False
# parse_args can take an explicit list (handy for testing)
args = parser.parse_args(["data.txt", "--limit", "5", "--verbose"])
print(args.filename) # data.txt
print(args.limit) # 5
print(args.verbose) # TrueTip:
parse_args()readssys.argvby default, but you can pass a list —parse_args(["a", "b"])— which is perfect for tests and examples.
Useful add_argument options
Section titled “Useful add_argument options”| Option | Effect |
|---|---|
type=int | Convert the value (to int, float, etc.). |
default=... | Value used when the argument is omitted. |
required=True | Make an optional argument mandatory. |
choices=[...] | Restrict to a fixed set of values. |
action="store_true" | Boolean flag (no value needed). |
nargs="+" | Accept multiple values into a list. |
help="..." | Text shown in --help. |
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--mode", choices=["fast", "safe"], default="safe")
parser.add_argument("--files", nargs="+") # one or more values
args = parser.parse_args(["--mode", "fast", "--files", "a.txt", "b.txt"])
print(args.mode) # fast
print(args.files) # ['a.txt', 'b.txt']Short flags
Section titled “Short flags”Add a one-letter alias alongside the long name:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-n", "--number", type=int, default=1)
args = parser.parse_args(["-n", "7"])
print(args.number) # 7Common pitfalls
Section titled “Common pitfalls”sys.argvvalues are strings — convert withint()/float().sys.argv[0]is the script name, not the first argument.- Dashes become underscores —
--max-sizeis read asargs.max_size. store_trueflags default toFalse— presence flips them toTrue.- argparse exits on bad input — it prints usage and calls
sys.exit, which is usually what you want.
Practice Exercises
Section titled “Practice Exercises”Exercise 1 – Read from a simulated argv
Section titled “Exercise 1 – Read from a simulated argv”Exercise 2 – Parse a positional argument
Section titled “Exercise 2 – Parse a positional argument”Exercise 3 – An optional integer with a default
Section titled “Exercise 3 – An optional integer with a default” flowchart TD
A["the command line"] --> B["sys.argv"]
B --> C["argv[0] is the SCRIPT name, not an argument"]
B --> D["every element is a string"]
D --> E["you convert and validate by hand"]
B --> F["parser.parse_args()"]
F --> G{"required missing, or a bad type?"}
G -->|yes| H["print usage, SystemExit(2)"]
G -->|no| I["a Namespace with converted values"]
I --> J["--dry-run becomes ns.dry_run"]
K["-h / --help"] --> L["print help, SystemExit(0)"]
Check yourself
Section titled “Check yourself”-
What is `sys.argv[0]`?
Verified: with no arguments, `len(sys.argv)` is 1. The first real argument is `sys.argv[1]`, which is the usual off-by-one in hand-rolled argument parsing.
pch.quizShowAnswer
B — The script name — Verified: with no arguments, `len(sys.argv)` is 1. The first real argument is `sys.argv[1]`, which is the usual off-by-one in hand-rolled argument parsing.
-
`--dry-run` with `action='store_true'`. How do you read it from the Namespace?
The dash becomes an underscore. `ns.dry-run` is a syntax error and `getattr(ns, 'dry-run')` returns nothing useful, so the flag appears to be ignored.
pch.quizShowAnswer
B — `ns.dry_run` — The dash becomes an underscore. `ns.dry-run` is a syntax error and `getattr(ns, 'dry-run')` returns nothing useful, so the flag appears to be ignored.
-
`-n abc` where `-n` was declared `type=int`. What does `parse_args()` do?
Verified. That is right for a command-line tool and wrong inside a library or a test — there, catch `SystemExit` or use `parse_known_args`.
pch.quizShowAnswer
C — Prints usage and raises `SystemExit(2)` — Verified. That is right for a command-line tool and wrong inside a library or a test — there, catch `SystemExit` or use `parse_known_args`.
-
What does argparse guarantee about the values your code receives?
A missing required argument or a bad conversion stops the program before your code runs, which is why a well-written `main()` needs no argument checking of its own.
pch.quizShowAnswer
B — They are present and already converted to the declared types — A missing required argument or a bad conversion stops the program before your code runs, which is why a well-written `main()` needs no argument checking of its own.
Summary
Section titled “Summary”sys.argvis the raw list of string arguments (argv[0]is the script name).argparseadds types, defaults, choices, flags, validation, and auto-generated--help.- Positional args are required;
--optionalargs can have defaults;store_truemakes boolean flags. - Pass an explicit list to
parse_args([...])for testing and examples.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading