Parse Command-Line Arguments in Python

Learn how to use Python's argparse module to parse command-line arguments, flags, and options for building CLI tools.

7 min read

The argparse module in the Python standard library parses command-line arguments, options, and flags. It replaces raw sys.argv parsing with a declarative API: you define what arguments your script accepts, and argparse handles parsing, validation, type conversion, error messages, and help text generation.

pythonpython
import argparse
 
parser = argparse.ArgumentParser(description="Greet someone by name.")
parser.add_argument("name", help="person to greet")
parser.add_argument("-c", "--count", type=int, default=1, help="number of greetings")
args = parser.parse_args()
 
for _ in range(args.count):
    print(f"Hello, {args.name}!")

Save this as greet.py and run it from the terminal with python greet.py Maya -c 3, passing "Maya" as the required name and 3 as the count. argparse handles converting "3" to an integer automatically:

texttext
Hello, Maya!
Hello, Maya!
Hello, Maya!

ArgumentParser(description=...) creates the parser with a description shown in help text. add_argument("name") defines a required positional argument.

add_argument("-c", "--count", type=int, default=1) defines an optional flag with automatic type conversion. parse_args() returns a namespace with each argument as an attribute.

Positional arguments

The simplest arguments to define are the ones every invocation must supply. Arguments without dashes are positional: they are required and identified by their order on the command line.

pythonpython
import argparse
 
parser = argparse.ArgumentParser()
parser.add_argument("input_file", help="path to the input file")
parser.add_argument("output_file", help="path to the output file")
args = parser.parse_args()
 
print(f"Reading from {args.input_file}")
print(f"Writing to {args.output_file}")

Running python convert.py data.csv output.json supplies both positional arguments in order, so argparse assigns them to input_file and output_file respectively:

texttext
Reading from data.csv
Writing to output.json

Positional arguments are required by default, and omitting one produces a usage error instead of a Python exception deep in your code. The user must provide them in the order they are defined.

Optional arguments and flags

Arguments prefixed with - or -- are optional. Use short flags (-c) for commonly used options and long flags (--count) for clarity in scripts.

pythonpython
import argparse
 
parser = argparse.ArgumentParser()
parser.add_argument("--host", default="localhost", help="server host")
parser.add_argument("--port", type=int, default=8080, help="server port")
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
args = parser.parse_args()
 
print(f"Server: {args.host}:{args.port}")
if args.verbose:
    print("Verbose mode enabled")

Running python server.py --host 0.0.0.0 --verbose overrides the host default and turns on the verbose flag, while --port keeps its default value since it was not passed:

texttext
Server: 0.0.0.0:8080
Verbose mode enabled

action="store_true" creates a boolean flag: it is False by default and True when the flag is present. No value follows the flag on the command line, which is what distinguishes a flag like --verbose from an option like --host that expects a value right after it.

Type conversion

Pass type to automatically convert argument strings to the correct type.

pythonpython
import argparse
 
parser = argparse.ArgumentParser()
parser.add_argument("--workers", type=int, default=4)
parser.add_argument("--threshold", type=float, default=0.95)
parser.add_argument("--numbers", type=int, nargs="+", default=[1, 2, 3])
args = parser.parse_args(["--workers", "8", "--threshold", "0.75", "--numbers", "10", "20", "30"])
 
print(args.workers, type(args.workers))
print(args.threshold, type(args.threshold))
print(args.numbers, type(args.numbers))

Each argument comes back already converted to the type you asked for, instead of the raw strings argparse would return by default if type were left unspecified:

texttext
8 <class 'int'>
0.75 <class 'float'>
[10, 20, 30] <class 'list'>

type=int converts the argument string to an integer, and type=float works the same way for decimal values. nargs="+" accepts one or more values and returns a list, which is useful for options that can take a variable number of arguments. Other nargs options control exactly how many values an argument consumes:

nargsMeaning
"?"Zero or one value (useful with const)
"*"Zero or more values
"+"One or more values
NExactly N values

Choices and validation

Some arguments only make sense within a fixed set of options, like a logging level or an output format. Restrict an argument to a set of allowed values with choices, and argparse will validate it automatically.

pythonpython
import argparse
 
parser = argparse.ArgumentParser()
parser.add_argument("--level", choices=["DEBUG", "INFO", "WARN", "ERROR"], default="INFO")
args = parser.parse_args(["--level", "DEBUG"])
 
print(args.level)  # DEBUG

If the user provides a value not in the list, argparse prints an error message listing the valid choices and exits before your script's own logic ever runs. This is cleaner than validating after parsing, since you do not have to write that check yourself or worry about forgetting it.

Auto-generated help

argparse automatically creates a --help (or -h) flag that prints usage information, built entirely from the arguments and help text you already defined, without any extra work on your part.

pythonpython
import argparse
 
parser = argparse.ArgumentParser(
    description="Process and transform data files.",
    epilog="Report issues at https://example.com/support",
)
parser.add_argument("input", help="input file path")
parser.add_argument("--format", choices=["json", "csv", "yaml"], default="json", help="output format")
args = parser.parse_args(["--help"])

Since the script above passes ["--help"] directly to parse_args, it behaves exactly as if a user ran it from the terminal with the --help flag, printing the following usage summary and exiting:

texttext
usage: script.py [-h] [--format {json,csv,yaml}] input
 
Process and transform data files.
 
positional arguments:
  input                 input file path
 
options:
  -h, --help            show this help message and exit
  --format {json,csv,yaml}
                        output format
 
Report issues at https://example.com/support

description appears at the top, right below the usage line. epilog appears after the argument list, which makes it a natural place for links or contact information. Each argument's help text appears next to its own entry, so users can see what every option does without reading any documentation outside the terminal.

Subcommands

Use add_subparsers() for CLI tools with multiple commands, like git commit or docker run.

pythonpython
import argparse
 
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command", required=True)
 
start_parser = subparsers.add_parser("start", help="start the server")
start_parser.add_argument("--port", type=int, default=8080)
 
stop_parser = subparsers.add_parser("stop", help="stop the server")
stop_parser.add_argument("--force", action="store_true")
 
args = parser.parse_args(["start", "--port", "3000"])
print(f"Command: {args.command}, Port: {args.port}")

Passing "start" as the subcommand routes the remaining arguments to start_parser, so --port 3000 is parsed using that subparser's own definition:

texttext
Command: start, Port: 3000

Each subparser has its own independent set of arguments, so --port only makes sense after start and --force only makes sense after stop. dest="command" stores the subcommand name so you can branch on it in your own code. required=True (Python 3.7+) makes the subcommand mandatory, so running the script with no subcommand at all produces a usage error instead of silently doing nothing.

Practical example: file processing CLI

Build a CLI tool that reads a file and outputs it in different formats.

pythonpython
import argparse
import json
import csv
from pathlib import Path
 
parser = argparse.ArgumentParser(description="Convert a CSV file to JSON.")
parser.add_argument("input", help="input CSV file")
parser.add_argument("-o", "--output", help="output file (default: stdout)")
parser.add_argument("--pretty", action="store_true", help="pretty-print JSON")
args = parser.parse_args()
 
with open(args.input, newline="") as file:
    reader = csv.DictReader(file)
    rows = list(reader)

With the rows loaded from the CSV file, convert them to a JSON string and either print it to the terminal or write it to the output path the user requested, depending on whether --output was provided:

pythonpython
indent = 2 if args.pretty else None
result = json.dumps(rows, indent=indent)
 
if args.output:
    Path(args.output).write_text(result)
    print(f"Wrote {len(rows)} rows to {args.output}")
else:
    print(result)

Running python convert.py data.csv -o output.json --pretty reads data.csv, converts every row to JSON, and writes the pretty-printed result to output.json instead of the terminal:

texttext
Wrote 150 rows to output.json

The script combines argparse for the CLI with reading CSV files and json for data processing, showing how a small amount of argument parsing can drive a genuinely useful command-line tool. The --output and --pretty flags are both optional, so the script still works with just a single required input file.

Common mistakes

Forgetting that positional arguments are required. If an argument has no - or -- prefix, the user must provide it, and argparse exits with a usage error if it is missing. Use nargs="?" and a default to make a positional argument optional instead of required.

Not using type conversion. Without type, all arguments are strings, even ones that look like numbers, so comparing or doing arithmetic on them without converting first produces subtle bugs. Use type=int, type=float, or a custom conversion function to get the right type immediately during parsing.

Parsing twice. parse_args() consumes sys.argv by default, and calling it again later in the same run reads the same arguments a second time rather than fresh input. If you need to test parsing with custom arguments, pass them explicitly instead: parse_args(args=["--port", "3000"]).

Using argparse for scripts that are also imported. parse_args() exits on errors, which is appropriate for CLI scripts but breaks imports, since importing the module would immediately try to parse arguments and exit. If your module is both a script and a library, guard parse_args() behind if name == "main": so importing it never triggers argument parsing.

Rune AI

Rune AI

Key Insights

  • Use ArgumentParser(description=...) to create a parser with automatic --help.
  • Use add_argument('name') for required positional arguments.
  • Use add_argument('--flag') for optional arguments and flags.
  • Set type=int or type=float to convert arguments automatically.
  • Use add_subparsers() for CLI tools with multiple commands.
  • Access parsed values with args.argument_name.
RunePowered by Rune AI

Frequently Asked Questions

Should I use argparse or sys.argv directly?

Use `argparse` for any script with more than one or two simple arguments. `argparse` handles validation, type conversion, default values, help text generation, and error messages automatically. `sys.argv` is only appropriate for the simplest cases where you need exactly one or two positional arguments.

How do I make an argument optional?

Prefix the argument name with `--` or `-` in `add_argument()`. For example, `parser.add_argument('--verbose', action='store_true')` creates an optional flag. Positional arguments (names without dashes) are required by default.

Can argparse handle subcommands like 'git commit'?

Yes. Use `parser.add_subparsers()` to create subcommand parsers. Each subcommand gets its own argument parser with its own arguments. This is how tools like `git`, `docker`, and `pip` structure their command-line interfaces.

Conclusion

argparse is the standard way to build command-line interfaces in Python. Use add_argument() to define positional arguments, optional flags, and typed options. Add --help automatically by passing a description. For tools with multiple commands, use subparsers. For simple one-off scripts, sys.argv is enough; for anything users will run repeatedly, invest in argparse.