Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Parse CLI args with argparse

Learn how to parse command-line arguments with argparse in this step-by-step Python tutorial for progressive mastery.

Focus: parse command-line arguments with argparse

Sponsored

Ever written a script that only works when you hardcode a filename or a database URL inside the code? That's a maintenance nightmare for you and a wall for anyone else who wants to reuse your tool. The argparse module is Python's built-in solution for building professional, flexible command-line interfaces (CLIs) that let users pass arguments like --output results.csv or -v, making your scripts robust, shareable, and ready for automation.

The problem this lesson solves

Hardcoded values are the enemy of reusable code. Imagine a Python script that processes a specific file every time:

# data_processor.py  —  hardcoded path, terrible
import csv

with open('input.csv') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

This works only for input.csv. To change the file, you edit the script. To control verbosity, you add a global variable. To set an output path, you guess. This is fragile, unsharable, and completely broken in automation pipelines.

The pain: - Scripts are not composable — you cannot chain them. - You need separate scripts for every variation. - No help message — users must read your code.

Pro tip: Every script that outlives your keyboard should have a CLI. argparse is the standard library tool for this job since Python 3.2.

Core concept / mental model

argparse turns your Python script into a function with named parameters — but from the command line. Think of it as a declarative specification for what your script accepts (flags, options, positional arguments) and a parser that automatically:

  1. Extracts values from sys.argv
  2. Converts strings to the desired type (int, float, file, etc.)
  3. Validates required vs. optional inputs
  4. Generates a help message (even docs!)

Key definitions: - Positional argument: Required by position, usually a value the command acts on (e.g., filename). - Optional argument / flag: Prefixed with -- or -, may be required or not (e.g., --verbose). - Parser: The ArgumentParser object that holds the specification. - Namespace: The parsed result — an object whose attributes are your argument names.

How it works step by step

Building a CLI with argparse is always a three-step dance:

1. Create the parser

import argparse

parser = argparse.ArgumentParser(
    description='Process some integers.'  # appears in --help
)

2. Add arguments

parser.add_argument('integers', metavar='N', type=int, nargs='+',
                    help='an integer for the accumulator')
parser.add_argument('--sum', dest='accumulate', action='store_const',
                    const=sum, default=max,
                    help='sum the integers (default: find the max)')

3. Parse and use the result

args = parser.parse_args()
print(args.accumulate(args.integers))  # args.integers is a list of ints

What happens under the hood: 1. sys.argv is inspected (e.g., ['program.py', '1', '2', '3', '--sum']) 2. Positional arguments are matched in order, with type coercion and error messages if needed. 3. Optional arguments are matched by prefix; --sum sets args.accumulate = sum. 4. A Namespace object is returned — you access values as attributes.

Hands-on walkthrough

Let's build a real script: a file stat tool that accepts a file, optionally output to JSON, and supports verbose mode.

Minimal example — greet the user

# greet.py
import argparse

parser = argparse.ArgumentParser(description='Greet someone.')
parser.add_argument('name', help='name of the person to greet')
parser.add_argument('--greeting', default='Hello',
                    help='custom greeting word (default: Hello)')

args = parser.parse_args()
print(f'{args.greeting}, {args.name}!')

Usage:

$ python greet.py Alice
Hello, Alice!

$ python greet.py Alice --greeting Hi
Hi, Alice!

$ python greet.py --help
usage: greet.py [-h] [--greeting GREETING] name

Greet someone.

positional arguments:
  name               name of the person to greet

optional arguments:
  -h, --help         show this help message and exit
  --greeting GREETING  custom greeting word (default: Hello)

File stat tool (advanced)

# filestat.py
import argparse
import os

parser = argparse.ArgumentParser(
    description='Print file statistics.'
)
parser.add_argument('file', help='path to the file')
parser.add_argument('--json', action='store_true',
                    help='output as JSON')
parser.add_argument('--verbose', '-v', action='count', default=0,
                    help='increase verbosity level')

args = parser.parse_args()

stat = os.stat(args.file)
size = stat.st_size
modified = stat.st_mtime

if args.json:
    import json
    result = {"file": args.file, "size": size, "modified": modified}
    print(json.dumps(result, indent=2))
else:
    print(f'File: {args.file}')
    print(f'Size: {size} bytes')
    print(f'Modified: {modified}')

if args.verbose >= 2:
    print(f'Inode: {stat.st_ino}')
    print(f'Permissions: {oct(stat.st_mode)}')

Usage:

$ python filestat.py README.md
File: README.md
Size: 1234 bytes
Modified: 1691234567.89

$ python filestat.py README.md --json -vv
{
  "file": "README.md",
  "size": 1234,
  "modified": 1691234567.89
}
Inode: 123456789
Permissions: 0o100644

Adding type checking and choices

# convert.py
import argparse

parser = argparse.ArgumentParser(description='Convert temperature units.')
parser.add_argument('temperature', type=float, help='temperature value')
parser.add_argument('--from', dest='from_unit', choices=['C', 'F', 'K'],
                    default='C', help='input unit (default: C)')
parser.add_argument('--to', dest='to_unit', choices=['C', 'F', 'K'],
                    default='F', help='output unit (default: F)')

args = parser.parse_args()

# conversion logic omitted for brevity
print(f"{args.temperature} {args.from_unit} -> {args.to_unit}")

Compare options / when to choose what

argparse is not your only CLI option. Here's how it stacks up against alternatives:

Library Pros Cons Best for
argparse Built-in, no dependencies, full-featured, automatic help Verbose syntax Standard scripts, libraries, everything in the standard library
click Decorator-based, composable, less boilerplate External dependency Complex CLIs with subcommands
typer FastAPI-style, type hints, minimal code External dependency, newer Type-safe CLIs with Python 3.6+
sys.argv No imports, simple Manual parsing, no help, error-prone One-off prototypes

When to choose argparse: - You need no extra dependencies. - Your script has 1–10 arguments. - You want auto-generated --help. - You work in a CI/CD environment where installing extra packages is undesirable.

Troubleshooting & edge cases

Common mistakes and how to fix them

1. Forgetting default when using nargs='?'

parser.add_argument('--verbose', nargs='?', const=1)
# If you run without --verbose, args.verbose is None, not 0.
# Fix: add default=0

2. Using type=bool — not what you think

parser.add_argument('--debug', type=bool)
# This converts any string to bool. Empty string is False, non-empty is True.
# Use action='store_true' for flags.

3. Positional arguments are always required unless nargs='?' - If you want an optional positional, use nargs='?' and default.

4. Argument name conflicts with reserved attribute names - If you store to args.type or args.help, Python gets angry. Use dest='...' to change the attribute name.

5. Parsing partial command lines (testing) - Use parser.parse_args(['--verbose', '5']) in unit tests instead of mocking sys.argv.

What you learned & what's next

You now know how to parse command-line arguments with argparse: - The mental model of a parser as a declarative spec. - The three-step workflow: create → add → parse. - How to handle positional vs optional arguments. - Type coercion, choices, and flags. - Real-world patterns (file paths, verbosity, JSON output). - How argparse compares to other CLI libraries. - Debugging common pitfalls.

Next step: In the next lesson, you'll learn how to generate and parse configuration files (like JSON, YAML, or TOML), so your scripts can be configured without even typing arguments — perfect for production pipelines.

Practice recap

Mini exercise: Write a script called stats.py that accepts one positional argument (a file path) and two optional flags: --lines (count lines) and --words (count words). If both are given, print both statistics. Use action='store_true' for both flags. Name your script, run it against a test file, and verify --help output is clear.

Common mistakes

  • Using type=bool for a flag — this converts any string to True; use action='store_true' or action='store_false' instead.
  • Forgetting to add default=0 when using action='count' — you'll get None instead of zero on first use.
  • Mixing positional arguments (always required) with options — if you want an optional positional, use nargs='?' and default.
  • Accidentally naming an argument that shadows an existing attribute on the Namespace (e.g., type, help) — use dest to rename.

Variations

  1. Use action='append' to collect multiple flag values into a list (e.g., --input a --input b).
  2. Use nargs='+' for one-or-more positional arguments, nargs='*' for zero-or-more.
  3. Combine argparse with subparsers to build multi-command CLIs (e.g., git commit, git push).

Real-world use cases

  • A data pipeline script that takes input/output file paths and a --verbose flag, used in a scheduled cron job.
  • A web scraping tool that accepts a search term (--query) and an output format (--json or --csv).
  • A CI/CD deploy script that takes environment (--env prod) and runs different steps based on optional flags.

Key takeaways

  • argparse turns command-line strings into Python objects with automatic help and error messages.
  • Always follow the three-step pattern: create a parser, add arguments, call parse_args().
  • Use action='store_true' for boolean flags, not type=bool.
  • Positional arguments are required by default; use nargs='?' or options for optional ones.
  • Choose argparse over click/typer when you need zero dependencies or a simple, standard CLI.
  • You can test argument parsing in isolation using parser.parse_args(['--flag', 'value']).

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.