Python Command-Line Argument Parsing with Argparse
Python is my go-to tool for command-line scripts, which often require passing command-line arguments. This comprehensive guide serves as a reference for command-line argument parsing in Python, covering everything from basic sys.argv usage to advanced argparse features.
Quick Start / TL;DR
- For simple scripts: Use
sys.argv[1]
to get the first argument - For robust CLI tools: Use
argparse
module (recommended) - Common pattern: Create ArgumentParser, add arguments, call parse_args()
- Best practices: Always include help text, validate input types, handle errors gracefully
import argparse
# Basic argparse setup
parser = argparse.ArgumentParser(description='Your script description')
parser.add_argument('filename', help='Input file path')
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose mode')
args = parser.parse_args()
print(f"Processing file: {args.filename}")
if args.verbose:
print("Verbose mode enabled")
Common Questions
How do I parse command line arguments in Python?
Python offers several ways to parse command-line arguments, with argparse
being the recommended approach for most cases:
import argparse
parser = argparse.ArgumentParser(description='Process some files')
parser.add_argument('files', nargs='+', help='Files to process')
parser.add_argument('--output', '-o', help='Output directory')
parser.add_argument('--verbose', '-v', action='store_true')
args = parser.parse_args()
print(f"Files to process: {args.files}")
print(f"Output directory: {args.output}")
print(f"Verbose mode: {args.verbose}")
What's the difference between argparse, sys.argv, and other parsing libraries?
Method | Best For | Pros | Cons |
---|---|---|---|
sys.argv |
Simple scripts with 1-2 args | Minimal code, direct access | No validation, no help generation |
argparse |
Most CLI applications | Rich features, auto-help, validation | More verbose setup |
getopt |
Legacy compatibility | Similar to C getopt | More complex, less intuitive |
click |
Complex CLI tools | Decorators, subcommands | External dependency |
When should I use positional vs optional arguments?
Use positional arguments for:
- Required inputs that are always needed
- Arguments with a natural order (input file, output file)
- Core functionality parameters
Use optional arguments for:
- Configuration flags (--verbose, --debug)
- Optional parameters with sensible defaults
- Behavior modifiers
parser = argparse.ArgumentParser()
# Positional: required input file
parser.add_argument('input_file', help='File to process')
# Optional: configuration flags
parser.add_argument('--output', '-o', default='output.txt', help='Output file')
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose mode')
How do I handle command line errors gracefully?
Argparse automatically handles many error cases, but you can add custom validation:
import argparse
import sys
from pathlib import Path
def validate_file(filepath):
"""Custom validation function"""
path = Path(filepath)
if not path.exists():
raise argparse.ArgumentTypeError(f"File '{filepath}' does not exist")
if not path.is_file():
raise argparse.ArgumentTypeError(f"'{filepath}' is not a file")
return path
parser = argparse.ArgumentParser()
parser.add_argument('input_file', type=validate_file, help='Input file')
try:
args = parser.parse_args()
print(f"Processing: {args.input_file}")
except SystemExit:
# argparse calls sys.exit() on error
print("Error: Invalid arguments provided")
sys.exit(1)
Why choose argparse over getopt or sys.argv?
argparse advantages:
- Automatic help generation (
--help
) - Type validation and conversion
- Error handling with clear messages
- Support for subcommands and complex argument structures
- Consistent, pythonic API
When to consider alternatives:
sys.argv
: Extremely simple scripts with minimal argumentsclick
: Complex CLI applications with subcommands and interactive featuresgetopt
: When maintaining legacy code or need C-style compatibility
Basic Example
First, you may not need a module. If all you want to do is grab a single argument and no flags or other parameters passed in, use the sys.argv
list that contains all of the command-line parameters.
The first element in sys.argv
is the script's name. So a parameter passed in will be the second element: sys.argv[1]
import sys
if len(sys.argv) > 1:
print(f"~ Script: {sys.argv[0]}")
print(f"~ Arg : {sys.argv[1]}")
else:
print("No arguments")
Saving as test.py
and running gives:
$ python test.py Foo
~ Script: test.py
~ Arg : Foo
Multiple Arguments with sys.argv
Since sys.argv
is simply a list, you can grab blocks of arguments together or slice around as you would any other list.
Last argument: sys.argv[-1]
All args after first: " ".join(sys.argv[2:])
Example with multiple arguments:
import sys
if len(sys.argv) > 1:
print(f"Script: {sys.argv[0]}")
print(f"All arguments: {sys.argv[1:]}")
print(f"Last argument: {sys.argv[-1]}")
print(f"Arguments 2-end: {' '.join(sys.argv[2:])}")
else:
print("No arguments provided")
Output:
$ python test.py first second third
Script: test.py
All arguments: ['first', 'second', 'third']
Last argument: third
Arguments 2-end: second third
How to Use Argparse for Flag Parameters
Use argparse
when you want to include flags (e.g., --help
), handle optional arguments, or
manage arguments with varying lengths. Argparse provides a robust framework for building command-line interfaces.
How to Create Boolean Flags (Help and Verbose Examples)
Boolean flags are one of the most common argument types. They're either present (True) or absent (False):
import argparse
parser = argparse.ArgumentParser(description='Demo script with verbose flag')
parser.add_argument('--verbose',
action='store_true',
help='Verbose mode')
args = parser.parse_args()
if args.verbose:
print("~ Verbose mode!")
else:
print("~ Running in normal mode")
Here's how to run the above example:
$ python test.py
~ Not so verbose
$ python test.py --verbose
~ Verbose!
The action parameter tells argparse
to store True if the flag is found, otherwise it stores False. A great benefit of using argparse
is the built-in help. Try it out by passing in an unknown parameter, -h
or --help
$ python test.py --help
usage: test.py [-h] [--verbose]
Demo
optional arguments:
-h, --help show this help message and exit
--verbose verbose output
Extended help
If you want to add more information to the automatically generated help message use the epilog
parameter when creating the ArgumentParser
object.
parser = argparse.ArgumentParser(
description='Demo',
epilog="My extended help text"
)
By default, regardless of your formatting argparse will strip all whitespace in epilog
and display as a single long string. If you want it to keep the whitespace, use the RawDescriptionHelpFormatter
like so:
parser = argparse.ArgumentParser(
description='Demo',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
My longer text
includes fancy
whitespace formatting
"""
)
Parameter error checking
A side effect of using argparse, you will get an error if a user passes in a command-line argument not expected, this includes flags or just an extra argument.
$ python test.py filename
usage: test.py [-h] [--verbose]
test.py: error: unrecognized arguments: filename
Multiple, Short or Long Flags
You can specify multiple flags for one argument, typically this is down with short and long flags, such as --verbose
and -v
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', '-v',
action='store_true',
help='verbose flag' )
args = parser.parse_args()
if args.verbose:
print("~ Verbose!")
else:
print("~ Not so verbose")
Required Flags
You can make a flag required by setting, required=True
this will cause an error if the flag is not specified.
parser = argparse.ArgumentParser()
parser.add_argument('--limit', required=True, type=int)
args = parser.parse_args()
Positional Arguments
The examples so far have been about flags, parameters starting with --
, argparse also handles the positional args which are just specified without the flag. Here's an example to illustrate.
parser = argparse.ArgumentParser()
parser.add_argument('filename', help='Name of the file to process')
args = parser.parse_args()
print(f"~ Filename: {args.filename}")
Output:
$ python test.py filename.txt
~ Filename: filename.txt
Number of Arguments
Argparse determines the number of arguments based on the action specified, for our verbose example, the store_true
action takes no argument. By default, argparse will look for a single argument, shown above in the filename example.
If you want your parameters to accept a list of items you can specify nargs=n
for how many arguments to accept. Note, if you set nargs=1
, it will return as a list not a single value.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('nums', nargs=2)
args = parser.parse_args()
print("~ Nums: {}".format(args.nums))
Output:
$ python test.py 5 2
~ Nums: ['5', '2']
Variable Number of Parameters
The nargs argument accepts a couple of extra special parameters. If you want the argument to accept all of the parameters, you can use *
which will return all parameters if present, or empty list if none.
parser = argparse.ArgumentParser()
parser.add_argument('nums', nargs='*')
args = parser.parse_args()
print("~ Nums: {}".format(args.nums))
Output:
$ python test.py 5 2 4
~ Nums: ['5', '2', '4']
If you want to require, 1 or more parameters, use nargs='+'
Positional arguments are determined by the position specified. This can be combined with the nargs='*'
for example if you want to define a filename and a list of values to store.
parser = argparse.ArgumentParser()
parser.add_argument('filename')
parser.add_argument('nums', nargs='*')
args = parser.parse_args()
print("~ Filename: {}".format(args.filename))
print("~ Nums: {}".format(args.nums))
Output:
$ python test.py file.txt 5 2 4
~ Fileanme: file.txt
~ Nums: ['5', '2', '4']
You can also specify nargs='?'
if you want to make a positional argument optional, but you need to be careful how you combine ? and * parameters, especially if you put an optional positional parameter before another one.
This makes sense, not requiring the last args:
parser = argparse.ArgumentParser()
parser.add_argument('filename')
parser.add_argument('nums', nargs='?')
args = parser.parse_args()
Output:
$ python test.py test.txt 3
~ Filename: test.txt
~ Nums: 3
$ python test.py test.txt
~ Filename: test.txt
~ Nums: None
However, using the nargs='?'
first will give unexpected results when arguments are missing, for example:
parser = argparse.ArgumentParser()
parser.add_argument('filename', nargs='?')
parser.add_argument('nums', nargs='*')
args = parser.parse_args()
Output:
$ python test.py 3 2 1
~ Filename: 3
~ Nums: ['2', '1']
You can use nargs
with flag arguments as well.
parser = argparse.ArgumentParser()
parser.add_argument('--geo', nargs=2)
parser.add_argument('--pos', nargs=2)
parser.add_argument('type')
args = parser.parse_args()
Output:
$ python test.py --geo 5 10 --pos 100 50 square
~ Geo: ['5', '10']
~ Pos: ['100', '50']
~ Type: square
Variable Type
You might notice that the parameters passed in are being treated like strings and not numbers, you can specify the variable type by specifying type=int
. By specifying the type, argparse will also fail if an invalid type is passed in.
parser = argparse.ArgumentParser()
parser.add_argument('nums', nargs=2, type=int)
args = parser.parse_args()
print("~ Nums: {}".format(args.nums))
Output:
$ python test.py 5 2
~ Nums: [5, 2]
File Types
Argparse has built-in filetypes that make it easier to open files specified on the command line. Here's an example of reading a file, you can do the same writing a file.
Traditional approach with argparse.FileType:
parser = argparse.ArgumentParser()
parser.add_argument('input_file', type=argparse.FileType('r'), help='Input file to read')
args = parser.parse_args()
for line in args.input_file:
print(line.strip())
args.input_file.close() # Don't forget to close
Modern approach with pathlib (recommended):
import argparse
from pathlib import Path
def validate_input_file(filepath):
"""Validate that the file exists and is readable"""
path = Path(filepath)
if not path.exists():
raise argparse.ArgumentTypeError(f"File '{filepath}' does not exist")
if not path.is_file():
raise argparse.ArgumentTypeError(f"'{filepath}' is not a file")
return path
parser = argparse.ArgumentParser()
parser.add_argument('input_file', type=validate_input_file, help='Input file to read')
args = parser.parse_args()
# Use pathlib's read_text() or open with context manager
content = args.input_file.read_text()
print(content)
# Or for line-by-line processing:
with args.input_file.open() as f:
for line in f:
print(line.strip())
Default Value
You may specify a default value if the user does not pass one in. Here's an example using a flag.
parser = argparse.ArgumentParser()
parser.add_argument('--limit', default=5, type=int)
args = parser.parse_args()
print("~ Limit: {}".format(args.limit))
Output:
$ python test.py
~ Limit: 5
Remainder
If you want to gather the extra arguments passed in, you can use remainder which gathers up all arguments not specified into a list.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--verbose',
action='store_true',
help='verbose flag' )
parser.add_argument('args', nargs=argparse.REMAINDER)
args = parser.parse_args()
print(args.args)
Specifying remainder will create a list of all remaining arguments:
$ python test.py --verbose foo bar
['foo', 'bar']
Actions
The default action is to assign the variable specified, but there are a couple of other actions that can be specified.
Booleans
We have already seen the boolean flag action which is action='store_true'
which also has a counter action for action='store_false'
Count
You can use the count action, which will return how many times a flag was called, this can be useful for verbosity or silent flags.
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', '-v', action='count')
args = parser.parse_args()
print("~ Verbose: {}".format(args.verbose))
Output:
$ python test.py
~ Verbose: None
$ python test.py --verbose
~ Verbose: 1
$ python test.py --verbose -v --verbose
~ Verbose: 3
Append
You can also use the append action to create a list if multiple flags are passed in.
parser = argparse.ArgumentParser()
parser.add_argument('-c', action='append')
args = parser.parse_args()
print("~ C: {}".format(args.c))
Output:
$ python test.py
~ C: None
$ python test.py -c hi
~ C: ['hi']
$ python test.py -c hi -c hello -c hey
~ C: ['hi', 'hello', 'hey']
Choices
If you only want a set of allowed values to be used, you can set the choices list, which will display an error if invalid entry.
parser = argparse.ArgumentParser(prog='roshambo.py')
parser.add_argument('throw', choices=['rock', 'paper', 'scissors'])
args = parser.parse_args()
print("~ Throw: {}".format(args.throw))
Examples
I'll end with two complete examples; many of the examples above are not as complete, they were kept short to focus on the idea being illustrated.
Copy Script Example
import argparse
import sys
parser = argparse.ArgumentParser(description='script to copy one file to another')
parser.add_argument('-v', '--verbose',
action="store_true",
help="verbose output" )
parser.add_argument('-R',
action="store_false",
help="Copy all files and directories recursively")
parser.add_argument('infile',
type=argparse.FileType('r'),
help="file to be copied")
parser.add_argument('outfile',
type=argparse.FileType('w'),
help="file to be created")
args = parser.parse_args()
Bug Script Example
Here is an example of a script that closes a bug
import argparse
import sys
parser = argparse.ArgumentParser(description='close bug')
parser.add_argument('-v', '--verbose',
action="store_true",
help="verbose output" )
parser.add_argument('-s',
default="closed",
choices=['closed', 'wontfix', 'notabug'],
help="bug status")
parser.add_argument('bugnum',
type=int,
help="Bug number to be closed")
parser.add_argument('message',
nargs='*',
help="optional message")
args = parser.parse_args()
print(f"~ Bug Num: {args.bugnum}")
print(f"~ Verbose: {args.verbose}")
print(f"~ Status : {args.s}")
print(f"~ Message: {' '.join(args.message)}")
Advanced Usage and Best Practices
How to Handle Subcommands
For complex CLI tools, you might want to support subcommands—these are commands that perform different actions, similar to how git
has subcommands like add
and commit
.
Argparse makes it easy to define these subcommands using the add_subparsers()
method. Each subcommand can have its own set of arguments and help text, allowing you to build flexible, user-friendly command-line interfaces. The example below demonstrates how to set up a parser with two subcommands, add
and commit
, each with their own arguments.
import argparse
parser = argparse.ArgumentParser(description='Git-like tool')
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# Add subparser
add_parser = subparsers.add_parser('add', help='Add files')
add_parser.add_argument('files', nargs='+', help='Files to add')
# Commit subparser
commit_parser = subparsers.add_parser('commit', help='Commit changes')
commit_parser.add_argument('-m', '--message', required=True, help='Commit message')
args = parser.parse_args()
if args.command == 'add':
print(f"Adding files: {args.files}")
elif args.command == 'commit':
print(f"Committing with message: {args.message}")
else:
parser.print_help()
What if I need environment variable fallbacks?
import argparse
import os
parser = argparse.ArgumentParser()
parser.add_argument('--output-dir',
default=os.environ.get('OUTPUT_DIR', './output'),
help='Output directory (default: ./output or $OUTPUT_DIR)')
parser.add_argument('--api-key',
default=os.environ.get('API_KEY'),
help='API key (can be set via $API_KEY environment variable)')
args = parser.parse_args()
if not args.api_key:
parser.error("API key is required. Set --api-key or $API_KEY environment variable")
Troubleshooting Common Argparse Issues
Common Error Messages and Solutions
Error: unrecognized arguments
script.py: error: unrecognized arguments: --typo
Solution: Check argument spelling, ensure all arguments are defined, or use parse_known_args()
if you need to handle unknown arguments.
Error: the following arguments are required
script.py: error: the following arguments are required: filename
Solution: Either provide the required positional argument or make it optional with nargs='?'
and a default value.
Error: argument --count: invalid int value
script.py: error: argument --count: invalid int value: 'abc'
Solution: Provide a valid integer, or add custom validation with better error messages.
Debugging Argparse Issues
1. Print parsed arguments for debugging:
args = parser.parse_args()
print(f"DEBUG: Parsed arguments: {vars(args)}") # Convert namespace to dict
2. Use parse_known_args()
for partial parsing:
args, unknown = parser.parse_known_args()
print(f"Known args: {args}")
print(f"Unknown args: {unknown}")
3. Test argument parsing in isolation:
import sys
# Save original sys.argv
original_argv = sys.argv.copy()
# Test with specific arguments
sys.argv = ['script.py', '--verbose', 'file.txt']
args = parser.parse_args()
print(args)
# Restore original
sys.argv = original_argv
Common "What If" Scenarios
What if I need to validate argument combinations?
parser = argparse.ArgumentParser()
parser.add_argument('--input-file')
parser.add_argument('--input-dir')
parser.add_argument('--recursive', action='store_true')
args = parser.parse_args()
# Custom validation after parsing
if not args.input_file and not args.input_dir:
parser.error("Must specify either --input-file or --input-dir")
if args.recursive and not args.input_dir:
parser.error("--recursive can only be used with --input-dir")
What if I need to modify arguments after parsing?
args = parser.parse_args()
# Convert relative paths to absolute
if args.input_file:
args.input_file = os.path.abspath(args.input_file)
# Normalize and validate paths
if args.output_dir:
args.output_dir = Path(args.output_dir).expanduser().resolve()
args.output_dir.mkdir(parents=True, exist_ok=True)
What if I need to handle configuration files?
import argparse
import json
from pathlib import Path
def load_config(config_file):
"""Load configuration from JSON file"""
if config_file and Path(config_file).exists():
with open(config_file) as f:
return json.load(f)
return {}
parser = argparse.ArgumentParser()
parser.add_argument('--config', help='Configuration file path')
parser.add_argument('--verbose', action='store_true')
parser.add_argument('--output-dir', default='./output')
# Parse args once to get config file
args, remaining = parser.parse_known_args()
# Load config file and set defaults
config = load_config(args.config)
parser.set_defaults(**config)
# Parse again with config defaults
args = parser.parse_args(remaining)
Best Practices Summary
- Always provide help text for every argument
- Use type validation (
type=int
, custom validators) rather than manual conversion - Validate argument combinations after parsing when needed
- Use pathlib for file/directory handling
- Provide sensible defaults and document them
- Handle errors gracefully with clear error messages
- Use environment variables as fallbacks when appropriate
- Test edge cases like missing files, invalid input, empty arguments
Related Topics
- Working with Files in Python - File handling best practices
- Python Functions - Creating reusable command-line utilities
- Python Type Hints - Adding type hints to CLI functions
- Testing in Python - How to test command-line applications
Resources
- Official Argparse Documentation - the official documentation includes all available options and an example tutorial using argparse.