142. Argparse
Command-line argument parsing with argparse
142. Argparse
He adds command-line flags so he doesn’t have to edit the code every time.
🤔 Socratic question: How does
git commit -m "my message"know what-mmeans? How doesnpm install --save-devknow the difference fromnpm install? You’re about to learn the exact same mechanism.
🏛️ History: The Unix pipe
|was invented by Doug McIlroy at Bell Labs in 1964. The idea: make every program do one thing, and make them composable.ps aux | grep chrome | awk '{print $2}'— three programs, one pipeline. Every CLI tool ever built follows this philosophy. You’re joining a 60-year tradition.
python ram_manager.py --threshold 90 --top-n 15 --save
python ram_manager.py --verboseHe learns argparse:
import argparse
parser = argparse.ArgumentParser(description='Monitor RAM usage')
parser.add_argument('--threshold', type=int, default=80,
help='Alert when RAM exceeds this %%')
parser.add_argument('--top-n', type=int, default=10,
help='Number of processes to show')
parser.add_argument('--save', action='store_true',
help='Save report to disk')
args = parser.parse_args()Three things catch him out:
- Missing
type=int— without it,--threshold 90gives the string'90', not the integer90. Comparison'90' > 80always returnsTrue(string comparison), silently wrong. action='store_true'notstore_false—store_truesets the flag toTruewhen present,Falsewhen absent. That’s “enable verbose mode.”- Positional vs optional —
--threshold(optional, pass it or not) vsthreshold(positional, required). Flags use--.
💡 Real-world:
git,npm,pip,docker,kubectl,ffmpeg,curl— every single one of these tools uses argparse-style argument parsing. When you rungit commit -m "fix", git is parsing-mthe same way you just learned. You now understand how the tools you use every day are built.
💡 Fun fact: argparse replaced the older optparse module in Python 2.7 (2010). Before that, developers used sys.argv directly and wrote their own parsing loops. argparse introduced automatic --help generation — your parser documents itself for free. The --help flag is injected automatically; you never add it yourself.
⚠️ Watch out: Forgetting type=int is one of the most common argparse bugs. Without it, --threshold 90 stores the string '90'. Then '90' > 80 evaluates to True in Python 3 (string vs int comparison raises TypeError — but '90' > '80' is alphabetic and silently wrong). Always declare type=int for numeric arguments.
🤔 Think about it: parser.parse_args() returns a Namespace object. What happens if you call parser.parse_args() but never assign the result to args? How would you test a CLI function that calls parse_args() internally — and why does passing args explicitly to functions make testing easier?
⌨️ Aryan is tired of editing the source code every time he wants to change the threshold or output file. He tries argparse but gets positional args confused with options, forgets type=, and doesn’t wire parse_args() to main().
Learning objectives
- Use – options vs positional arguments
- Add type=int to auto-convert string input
- Use action=‘store_true’ for boolean flags
- Wire parse_args() result to your function
- Add help= text for –help output
Key concepts
- add_argument(‘–name’) — named option
- add_argument(‘name’) — positional argument
- type=int — auto type conversion
- action=‘store_true’ — boolean flag
- default= — value when flag is absent
- parse_args() — returns Namespace object
Try it
Concept detail
CLI Arguments with argparse
argparse is Python’s stdlib module for building command-line interfaces.
Basic setup
import argparse
parser = argparse.ArgumentParser(description='RAM Manager')
# Option (--name) — optional, has default
parser.add_argument('--threshold', type=int, default=80,
help='RAM %% alert threshold')
# Positional — required, no default
parser.add_argument('pid', type=int, help='Process ID to inspect')
args = parser.parse_args()
print(args.threshold, args.pid)Argument types
parser.add_argument('--count', type=int) # integer
parser.add_argument('--ratio', type=float) # float
parser.add_argument('--output', type=str) # string (default)
parser.add_argument('--verbose', action='store_true') # bool flag
parser.add_argument('--file', type=open) # opens the fileChoices and validation
parser.add_argument('--format', choices=['json', 'csv', 'text'], default='text')
parser.add_argument('--threshold', type=int, choices=range(1, 101))Subcommands
subparsers = parser.add_subparsers(dest='command')
monitor_p = subparsers.add_parser('monitor', help='Watch RAM')
monitor_p.add_argument('--threshold', type=int, default=80)
kill_p = subparsers.add_parser('kill', help='Kill a process')
kill_p.add_argument('pid', type=int)
args = parser.parse_args()
if args.command == 'monitor':
run_monitor(args.threshold)
elif args.command == 'kill':
kill_process(args.pid)Usage from terminal
python ram_manager.py --threshold 70 --top-n 10 --verbose
python ram_manager.py --help
python ram_manager.py monitor --threshold 70
python ram_manager.py kill 812argparse vs click
| argparse | click | |
|---|---|---|
| install | stdlib | pip install click |
| decorator style | no | yes |
| testing | manual | CliRunner |
| best for | simple scripts | larger CLIs |
Solution
import argparse
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description='RAM Manager — monitor memory and get AI advice'
)
# --threshold is an option (--name), not a positional argument
parser.add_argument(
'--threshold', type=int, default=80,
help='RAM %% to trigger alert (default: 80)'
)
parser.add_argument(
'--top-n', type=int, default=5,
help='Number of top processes to show (default: 5)'
)
parser.add_argument(
'--output', default=None,
help='Save JSON report to this file path'
)
# is_flag=True equivalent in argparse: action='store_true'
parser.add_argument(
'--verbose', action='store_true', default=False,
help='Show extra info'
)
return parser
def main():
parser = build_parser()
args = parser.parse_args()
monitor(
threshold=args.threshold,
top_n=args.top_n,
output=args.output,
verbose=args.verbose,
)
def monitor(threshold: int, top_n: int, output: str = None, verbose: bool = False):
print(f'threshold={threshold} top_n={top_n} output={output} verbose={verbose}')Tests
def test_default_threshold():
parser = build_parser()
args = parser.parse_args([])
assert args.threshold == 80
def test_custom_threshold():
parser = build_parser()
args = parser.parse_args(['--threshold', '70'])
assert args.threshold == 70
def test_threshold_is_int():
parser = build_parser()
args = parser.parse_args(['--threshold', '90'])
assert isinstance(args.threshold, int), '--threshold must be int not str'
def test_top_n_is_int():
parser = build_parser()
args = parser.parse_args(['--top-n', '10'])
assert isinstance(args.top_n, int), '--top-n must be int not str'
def test_verbose_is_bool():
parser = build_parser()
args = parser.parse_args(['--verbose'])
assert args.verbose is True
def test_verbose_default_false():
parser = build_parser()
args = parser.parse_args([])
assert args.verbose is False
def test_output_default_none():
parser = build_parser()
args = parser.parse_args([])
assert args.output is None
def test_threshold_not_positional():
parser = build_parser()
# positional would fail with no args; --threshold should work with default
args = parser.parse_args([])
assert args.threshold == 80, '--threshold should be optional with a default'