Building CLI Tools with argparse

· 4 min read · Updated March 13, 2026 · beginner
python stdlib cli argparse

If you’ve ever used a command-line tool in Python, you’ve probably passed arguments like --verbose or -f filename. These are handled by the argparse module, part of Python’s standard library. It’s the standard way to build CLIs that feel professional and behave like Unix tools.

Why argparse?

Before argparse, developers used sys.argv directly. It works, but you end up parsing strings manually, handling errors yourself, and producing unhelpful error messages. Argparse gives you:

  • Automatic help text generation
  • Type conversion and validation
  • Positional and optional arguments
  • Subcommands (like git commit vs git push)

It’s been in the standard library since Python 3.2, so there’s no excuse not to use it.

Your First argparse Script

The simplest possible argparse script:

import argparse

parser = argparse.ArgumentParser()
parser.parse_args()

Run it with --help and you’ll see:

$ python script.py --help
usage: script.py [-h]

options:
  -h, --help  show this help message and exit

Not very useful yet. Let’s add some arguments.

Positional Arguments

Positional arguments are required and identified by position. Think of python script.py filename where filename is positional:

import argparse

parser = argparse.ArgumentParser(description="Read a file")
parser.add_argument("filename", help="Name of the file to read")
args = parser.parse_args()

print(f"Reading from: {args.filename}")
$ python script.py myfile.txt
Reading from: myfile.txt

Multiple positional arguments work too:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("x", type=int, help="First number")
parser.add_argument("y", type=int, help="Second number")
args = parser.parse_args()

print(f"Sum: {args.x + args.y}")
$ python script.py 5 3
Sum: 8

Optional Arguments

Optional arguments start with - or --. They’re, well, optional:

import argparse

parser = argparse.ArgumentParser(description="Print a message")
parser.add_argument("--name", help="Name to greet")
args = parser.parse_args()

if args.name:
    print(f"Hello, {args.name}!")
else:
    print("Hello, World!")
$ python script.py
Hello, World!

$ python script.py --name Alice
Hello, Alice!

You can combine short and long forms:

parser.add_argument("-v", "--verbose", help="Enable verbose output")
$ python script.py -v
$ python script.py --verbose

Flags and Boolean Options

Need a simple on/off flag? Use action="store_true":

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-q", "--quiet", action="store_true", help="Suppress output")
args = parser.parse_args()

if args.quiet:
    print("Quiet mode on")
else:
    print("Normal mode")
$ python script.py
Normal mode

$ python script.py -q
Quiet mode on

For the opposite, use store_false.

Type Conversion

Argparse automatically converts types. Just specify type:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("count", type=int, help="Number of times to repeat")
parser.add_argument("rate", type=float, help="Repeat rate per second")
args = parser.parse_args()

print(f"Will repeat {args.count} times at {args.rate} per second")
$ python script.py 10 2.5
Will repeat 10 times at 2.5 per second

This also validates input - try entering “abc” instead of a number and argparse will error out with a helpful message.

Choices

Limit acceptable values with choices:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("level", choices=["easy", "medium", "hard"], help="Difficulty level")
args = parser.parse_args()

print(f"Selected level: {args.level}")
$ python script.py easy
Selected level: easy

$ python script.py impossible
usage: script.py [-h] {easy,medium,hard}
script.py: error: argument level: invalid choice: 'impossible' (choose from 'easy', 'medium', 'hard')

Default Values

Set defaults for optional arguments:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--count", type=int, default=1, help="Number of times")
parser.add_argument("--name", default="World", help="Name to greet")
args = parser.parse_args()

for i in range(args.count):
    print(f"Hello, {args.name}!")
$ python script.py
Hello, World!

$ python script.py --count 3 --name Alice
Hello, Alice!
Hello, Alice!
Hello, Alice!

Required Options

Sometimes you really do want an option to be required. Use required=True:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--config", required=True, help="Configuration file path")
args = parser.parse_args()

print(f"Using config: {args.config}")

Now the script won’t run without --config.

Argument Groups

Group related arguments together for better help text:

import argparse

parser = argparse.ArgumentParser(description="A mailer program")

group = parser.add_argument_group("email options")
group.add_argument("--to", help="Recipient email address")
group.add_argument("--from", dest="sender", help="Sender email address")
group.add_argument("--subject", help="Email subject")

args = parser.parse_args()

This organizes the help output logically.

Subcommands

Big CLI tools often have subcommands (like git commit, git push, git pull). Argparse supports this:

import argparse

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command", help="Available commands")

# 'add' subcommand
add_parser = subparsers.add_parser("add", help="Add numbers")
add_parser.add_argument("x", type=int)
add_parser.add_argument("y", type=int)

# 'multiply' subcommand
mult_parser = subparsers.add_parser("multiply", help="Multiply numbers")
mult_parser.add_argument("x", type=int)
mult_parser.add_argument("y", type=int)

args = parser.parse_args()

if args.command == "add":
    print(f"Result: {args.x + args.y}")
elif args.command == "multiply":
    print(f"Result: {args.x * args.y}")
$ python calc.py add 5 3
Result: 8

$ python calc.py multiply 5 3
Result: 15

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

positional arguments:
  {add,multiply}

options:
  -h, --help   show this help message and exit

ArgumentParser Basics

A few useful ArgumentParser options:

parser = argparse.ArgumentParser(
    description="My CLI tool",
    epilog="Example: mytool.py --verbose input.txt",
    formatter_class=argparse.RawDescriptionHelpFormatter,
)
  • description: Shown at the top of help
  • epilog: Shown at the bottom
  • formatter_class: Controls help formatting

Putting It Together

Here’s a more complete example combining several features:

#!/usr/bin/env python3
import argparse

def main():
    parser = argparse.ArgumentParser(
        description="Process a data file"
    )
    parser.add_argument(
        "input",
        help="Input file path"
    )
    parser.add_argument(
        "-o", "--output",
        default="output.txt",
        help="Output file path (default: output.txt)"
    )
    parser.add_argument(
        "-v", "--verbose",
        action="store_true",
        help="Enable verbose output"
    )
    parser.add_argument(
        "-n", "--lines",
        type=int,
        default=10,
        help="Number of lines to process (default: 10)"
    )
    parser.add_argument(
        "--format",
        choices=["json", "csv", "xml"],
        default="json",
        help="Output format (default: json)"
    )

    args = parser.parse_args()

    if args.verbose:
        print(f"Processing: {args.input}")
        print(f"Output to: {args.output}")
        print(f"Format: {args.format}")
        print(f"Lines: {args.lines}")

    # Your processing logic here
    print(f"Done! Processed {args.lines} lines.")

if __name__ == "__main__":
    main()

Run with --help to see the complete, auto-generated interface.

See Also