Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Design a CLI with argparse

Learn to design a simple CLI application using Python's argparse module — a practical lesson for building command-line interfaces step by step.

Focus: design a simple cli application with argparse

Sponsored

Ever built a Python script that does something useful, only to realize it's locked to hardcoded values? You change a filename, edit the script, rerun — and repeat. That's a productivity killer. The fix is a proper command-line interface (CLI) — and Python's argparse module makes it dead simple. In this lesson, you'll learn to design a simple CLI application with argparse, moving from rigid scripts to flexible, professional tools that users (including future you) will love to run from the terminal.

The problem this lesson solves

Without a CLI argument parser, your Python scripts are inflexible. To change an input file, debug level, or output directory, users must edit the source code. That's error-prone, breaks automation, and makes your tool impossible to integrate into shell pipelines. argparse solves this by giving you a structured way to accept command-line arguments, flags, and options — automatically generating help messages, handling type conversion, and providing meaningful error messages when users mistype something.

Core concept / mental model

Think of argparse as a menu planner for your script. You define what ingredients (arguments) your script expects: required items (positional arguments), optional seasonings (optional arguments with flags like -o), and switches (boolean flags like --verbose). When a user runs your script from the terminal, argparse reads the command line, validates it against your menu, and returns a namespace object with all the parsed values. No manual parsing of sys.argv — ever.

Key terms

  • Positional argument: an argument that must be provided in order (e.g., a filename).
  • Optional argument: an argument introduced by a flag (e.g., -o output.txt).
  • Namespace: a simple object whose attributes hold the parsed values.
  • Parser: the ArgumentParser instance that defines the interface.

Pro tip: argparse is part of Python's standard library — no pip install needed. It works out of the box in Python 3.2+.

How it works step by step

1. Import and create a parser

Start by importing argparse and creating an ArgumentParser object. Give it a description — this shows up in the --help output.

import argparse

parser = argparse.ArgumentParser(description='Process some integers.')

2. Add arguments

Use .add_argument() to define each argument. The first argument is the name or flag. For positional arguments, just give a name (e.g., 'filenames'). For optional ones, use flag prefixes like - or -- (e.g., '--output', '-v').

parser.add_argument('filenames', nargs='+', help='Input files to process')
parser.add_argument('--output', '-o', help='Output file name')
parser.add_argument('--verbose', '-v', action='store_true', help='Increase output verbosity')
  • nargs='+' means one or more positional values.
  • action='store_true' stores True if the flag is present, False otherwise.

3. Parse the command line

Call .parse_args() — it reads sys.argv automatically and returns a namespace.

args = parser.parse_args()

Now args.filenames, args.output, and args.verbose are available.

4. Use the values

Access parsed values as attributes of args and write your script logic.

if args.verbose:
    print('Verbose mode enabled')
print(f"Processing files: {args.filenames}")
print(f"Output to: {args.output or 'stdout'}")

That's the skeleton. Now let's build a real tool.

Hands-on walkthrough

Example 1: A file duplication tool (basic)

Create a script dup.py that copies a file to a new name.

import argparse
import shutil
import os

parser = argparse.ArgumentParser(description='Duplicate a file with an optional new name.')
parser.add_argument('source', help='Source file to copy')
parser.add_argument('dest', nargs='?', default=None, help='Destination filename (default: source name with _copy)')

args = parser.parse_args()
dest = args.dest or f"{os.path.splitext(args.source)[0]}_copy{os.path.splitext(args.source)[1]}"

try:
    shutil.copy2(args.source, dest)
    print(f"Copied {args.source} to {dest}")
except FileNotFoundError:
    print(f"Error: Source file '{args.source}' not found.")
    exit(1)

Run it:

$ python dup.py sample.txt
Copied sample.txt to sample_copy.txt

$ python dup.py sample.txt backup.txt
Copied sample.txt to backup.txt

$ python dup.py missing.txt
Error: Source file 'missing.txt' not found.

Notice: nargs='?' makes dest optional. default=None lets us set a sensible default.

Example 2: Log file analyzer with options

A script that scans a log file for error counts, with optional verbosity and output control.

import argparse
import re

def main():
    parser = argparse.ArgumentParser(description='Analyze log files for error counts.')
    parser.add_argument('logfile', help='Path to the log file')
    parser.add_argument('--pattern', '-p', default='ERROR|Error|error', help='Regex pattern to count (default: ERROR)')
    parser.add_argument('--output', '-o', help='Save results to this file')
    parser.add_argument('--verbose', '-v', action='store_true', help='Print summary during processing')

    args = parser.parse_args()

    try:
        with open(args.logfile, 'r') as f:
            content = f.read()
        matches = re.findall(args.pattern, content)
        count = len(matches)

        output_lines = f"File: {args.logfile}\nPattern: {args.pattern}\nMatches: {count}\n"
        if args.verbose:
            print(output_lines)
        if args.output:
            with open(args.output, 'w') as out:
                out.write(output_lines)
    except FileNotFoundError:
        print("Error: Log file not found.")
        exit(1)

if __name__ == '__main__':
    main()

Run and test:

$ echo -e "INFO: start\nERROR: timeout\nINFO: done" > demo.log
$ python log_analyzer.py demo.log -v -o results.txt
File: demo.log
Pattern: ERROR|Error|error
Matches: 1

$ cat results.txt
File: demo.log
Pattern: ERROR|Error|error
Matches: 1

Example 3: Calculator with subcommands

For tools that do multiple things (like git), use subparsers.

import argparse

def main():
    parser = argparse.ArgumentParser(description='Simple calculator with subcommands.')
    subparsers = parser.add_subparsers(dest='command', required=True)

    # Add subcommand
    add_parser = subparsers.add_parser('add', help='Add two numbers')
    add_parser.add_argument('x', type=float, help='First number')
    add_parser.add_argument('y', type=float, help='Second number')

    # Subtract subcommand
    sub_parser = subparsers.add_parser('sub', help='Subtract two numbers')
    sub_parser.add_argument('x', type=float, help='First number')
    sub_parser.add_argument('y', type=float, help='Second number')

    args = parser.parse_args()

    if args.command == 'add':
        print(args.x + args.y)
    elif args.command == 'sub':
        print(args.x - args.y)

if __name__ == '__main__':
    main()
$ python calc.py add 3 4
7.0

$ python calc.py sub 10 2.5
7.5

$ python calc.py --help
usage: calc.py [-h] {add,sub} ...

Simple calculator with subcommands.

positional arguments:
  {add,sub}   available subcommands
    add       Add two numbers
    sub       Subtract two numbers

Compare options / when to choose what

Approach Use case Pros Cons
argparse Standard CLI tools (most Python scripts) Built-in, powerful, help generation, subcommands Verbose for very simple needs
click (3rd party) Modern, decorator-based CLIs Concise, auto-aware of types, nested commands Extra dependency (pip install click)
sys.argv Quick one-liners or scripts with ≤2 arguments No imports, trivial setup No help, no type conversion, messy
typer (3rd party) Type-hint-driven CLIs Pythonic, zero boilerplate, async support Requires third-party library

Recommendation: Start with argparse — it's always available, battle-tested, and covers 90% of CLI needs. Switch to click or typer when you hit complex nested commands or want a more declarative style.

Troubleshooting & edge cases

  • Unrecognized arguments: If you misspell a flag, argparse prints an error and exits with a usage message. Always check the help output. bash $ python dup.py --verbos usage: dup.py [-h] [-o OUTPUT] source [dest] dup.py: error: unrecognized arguments: --verbos
  • Missing required positional arguments: Forgetting to provide a positional argument triggers an error. bash $ python dup.py usage: dup.py [-h] [-o OUTPUT] source [dest] dup.py: error: the following arguments are required: source
  • Type conversion: Use type=int or type=float to auto-convert. If conversion fails, argparse gives a clear error. python parser.add_argument('count', type=int, help='Number of iterations')
  • Conflicting flags: Don't use the same short flag (-o) for two different arguments. If needed, define unique flags.
  • Subcommand default: If you set dest='command' with required=True, users must provide a subcommand — you can control error messages with set_defaults(func=...).

What you learned & what's next

You now know how to design a simple CLI application with argparse — from basic positional arguments to optional flags, subcommands, and user-friendly help. You've seen how argparse eliminates manual parsing, adds validation, and makes your scripts feel like real command-line tools.

You can now: - Explain the core idea behind argparse — defining a menu and parsing arguments. - Complete a practical exercise that adds arguments, handles errors, and produces a usable CLI.

Next step: Move on to "Create a Python package for distribution" — where you'll learn to bundle your CLI script into an installable package so anyone can run it with a single pip install. Keep that argparse-based tool handy; you'll use it as your package!

Practice recap

Mini-exercise: Build a CLI tool called greeter.py that accepts a positional name and an optional --greeting (default "Hello") and --count (default 1, type int). Print the greeting count times. Then add a --shout flag that uppercases the message. Test it with python greeter.py Alice --greeting "Hi" --count 3 --shout.

Common mistakes

  • Omitting type= causes all arguments to be strings — you must explicitly set type=int for numeric values.
  • Using --flag without action='store_true' leads to errors — argparse expects a value after a flag unless action is set.
  • Forgetting to call parser.parse_args() — nothing happens until you parse; you'll get a namespace object, not usable variables.
  • Mixing positional and optional arguments incorrectly — positional order matters, but optional can appear anywhere.
  • Ignoring the default Namespace object — attempting to access args.undefined_arg silently returns None instead of raising an error.

Variations

  1. Use nargs='*' for zero or more positional arguments — unlike nargs='+', empty lists are allowed.
  2. Add required=True to make an optional flag mandatory — useful for forcing explicit input without using positional arguments.
  3. Leverage add_subparsers() for multi-command tools (like Git) where each subcommand has its own arguments.

Real-world use cases

  • A log analysis tool that takes a log file and optional regex pattern, outputting error counts to stdout or a file.
  • A backup script accepting source and destination paths, with a --compress flag that enables tarball creation.
  • A database migration CLI with subcommands like 'upgrade', 'downgrade', and 'status', each with specific flags.

Key takeaways

  • argparse handles command-line argument parsing, validation, and help generation automatically.
  • Positional arguments are required by default; optional arguments use flag prefixes like -o or --output.
  • Use type=int, action='store_true', and nargs to control parsing behavior precisely.
  • Subparsers let you build multi-command interfaces (like Git) with independent argument sets per command.
  • Always call parser.parse_args() to trigger parsing — without it, your script ignores the command line.
  • Choose argparse for standard CLIs because it's built-in, powerful, and well-documented.

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.