← Home

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 -m means? How does npm install --save-dev know the difference from npm 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 --verbose

He 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:

  1. Missing type=int — without it, --threshold 90 gives the string '90', not the integer 90. Comparison '90' > 80 always returns True (string comparison), silently wrong.
  2. action='store_true' not store_falsestore_true sets the flag to True when present, False when absent. That’s “enable verbose mode.”
  3. Positional vs optional--threshold (optional, pass it or not) vs threshold (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 run git commit -m "fix", git is parsing -m the 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 file

Choices 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 812

argparse vs click

argparseclick
installstdlibpip install click
decorator stylenoyes
testingmanualCliRunner
best forsimple scriptslarger 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'

Resources