argparse Basics: Parsing Command-Line Arguments in Python
Intro context
argparse basics are the foundation of almost every Python CLI you’ve used. The argparse module lives in the standard library and handles the tedious work of parsing command-line arguments, generating --help output, and reporting usage errors with a non-zero exit code. Most Python CLIs you run (pip, pytest, black) use argparse under the hood, so learning argparse basics pays off whether you’re writing your own tool or reading someone else’s.
This guide walks through creating an ArgumentParser, adding positional and optional arguments, converting types, setting defaults, using action='store_true' for flags, handling unused flags that return None, and reading what argparse-generated help text looks like. For more advanced patterns once these argparse basics click, see the argparse guide and the Click framework tutorial.
Creating a parser
Start by importing argparse and creating an ArgumentParser object:
import argparse
parser = argparse.ArgumentParser(description="Read and print a file")
parser.add_argument('filename')
parser.add_argument('--verbose', '-v', action='store_true')
args = parser.parse_args()
print(args.filename)
print("Verbose:", args.verbose)
Run it:
$ python cli.py myfile.txt
myfile.txt
Verbose: False
$ python cli.py --verbose myfile.txt
myfile.txt
Verbose: True
parse_args() reads sys.argv[1:] by default and returns a Namespace object. Attributes on that object match the argument names you defined.
Positional arguments
Positional arguments come before the dashes and are required by default. The order matters:
parser = argparse.ArgumentParser()
parser.add_argument('input_file')
parser.add_argument('output_file')
args = parser.parse_args()
# python script.py data.csv result.csv
# args.input_file = "data.csv"
# args.output_file = "result.csv"
You can add type conversion so parse_args() validates and converts the value:
parser.add_argument('count', type=int)
parser.add_argument('rate', type=float)
args = parser.parse_args(['100', '0.5'])
# args.count = 100 (int)
# args.rate = 0.5 (float)
If the user passes an invalid type, argparse prints an error automatically:
$ python script.py abc
usage: script.py [-h] count
script.py: error: argument count: invalid int value: 'abc'
Optional arguments with flags
Optional arguments start with - for short flags and -- for long flags:
parser.add_argument('-n', '--name', default='World')
parser.add_argument('--count', type=int, default=1)
args = parser.parse_args([])
# args.name = 'World'
# args.count = 1
args = parser.parse_args(['--name', 'Alice', '--count', '5'])
# args.name = 'Alice'
# args.count = 5
Short flags can be combined:
$ python script.py -n Bob --count 3
Boolean flags (store_true / store_false)
Use action='store_true' for flags that don’t need a value:
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('-q', '--quiet', action='store_true')
args = parser.parse_args(['-v'])
# args.verbose = True
# args.quiet = False
action='store_false' is the inverse , the attribute is True by default and False when the flag appears:
parser.add_argument('--no-color', action='store_false')
parser.add_argument('--highlight', action='store_true', default=False)
# With --no-color on the command line:
# args.highlight = False (default)
# args.no_color = False (because --no-color was given)
Choices and validation
Restrict an argument to specific values:
parser.add_argument('--format', choices=['json', 'yaml', 'csv'])
args = parser.parse_args(['--format', 'json'])
# args.format = 'json'
# Invalid choice:
# $ python script.py --format xml
# error: argument --format: invalid choice: 'xml'
For numeric ranges, combine type=int with explicit validation:
parser.add_argument('--port', type=int, choices=range(1024, 65536))
# Only accepts 1024-65535
Required options
By default, optional arguments are , optional. To make one required:
parser.add_argument('--config', required=True)
This is relatively rare. Most of the time you want a sensible default instead.
Default values
Set defaults so the script runs without every argument being specified:
parser.add_argument('--output', default='output.txt')
parser.add_argument('--lines', type=int, default=10)
args = parser.parse_args([])
# args.output = 'output.txt'
# args.lines = 10
Use nargs='?' to distinguish between “not provided” and “explicitly set to default”:
parser.add_argument('--mode', nargs='?', default='auto', const='forced')
# No flag: args.mode = 'auto'
# --mode: args.mode = 'forced'
# --mode custom: args.mode = 'custom'
Counting values
Use nargs='+' to accept one or more values, or nargs='*' for zero or more:
parser.add_argument('files', nargs='+')
parser.add_argument('--tags', nargs='*', default=[])
args = parser.parse_args(['file1.txt', 'file2.txt', '--tags', 'urgent', 'review'])
# args.files = ['file1.txt', 'file2.txt']
# args.tags = ['urgent', 'review']
Help messages and —help
Add help= to make your CLI self-documenting:
parser = argparse.ArgumentParser(
description="Copy a file to a destination",
epilog="Report bugs to dev@example.com"
)
parser.add_argument('source', help="Path to the source file")
parser.add_argument('dest', help="Path to the destination")
parser.add_argument('--verbose', '-v', action='store_true', help="Print progress")
args = parser.parse_args()
Running with -h or --help produces:
usage: cp.py [-h] [--verbose] source dest
Copy a file to a destination
positional arguments:
source Path to the source file
dest Path to the destination
options:
-h, --help show this help message and exit
--verbose, -v Print progress
The --help flag is added automatically. argparse also adds -h as a shorthand.
Mutual exclusion
Put mutually exclusive arguments in a group:
group = parser.add_mutually_exclusive_group()
group.add_argument('--short', action='store_true')
group.add_argument('--long', action='store_true')
Using both --short and --long together produces an error:
$ python script.py --short --long
usage: script.py [-h] [--short | --long]
script.py: error: argument --long: not allowed with argument --short
Parsing without sys.argv
For testing or scripted use, pass a list directly to parse_args:
args = parser.parse_args(['--name', 'Alice', 'input.txt'])
# Does not touch sys.argv
This makes it easy to test your argument parsing logic.
A complete example
import argparse
def main():
parser = argparse.ArgumentParser(description="Grep-like text search")
parser.add_argument('pattern', help="Regex pattern to search for")
parser.add_argument('files', nargs='+', help="Files to search")
parser.add_argument('-i', '--ignore-case', action='store_true',
help="Case-insensitive matching")
parser.add_argument('-n', '--line-number', action='store_true',
help="Show line numbers")
parser.add_argument('-c', '--count', action='store_true',
help="Show only the count of matches per file")
args = parser.parse_args()
import re
flags = re.IGNORECASE if args.ignore_case else 0
pattern = re.compile(args.pattern, flags)
for filepath in args.files:
try:
with open(filepath) as f:
for lineno, line in enumerate(f, 1):
if pattern.search(line):
if args.count:
continue # just count for now
prefix = f"{filepath}:" if len(args.files) > 1 else ""
num_prefix = f"{lineno}:" if args.line_number else ""
print(f"{prefix}{num_prefix}{line.rstrip()}")
except FileNotFoundError:
print(f"Skipping {filepath}: not found")
if __name__ == '__main__':
main()
Common pitfalls
Getting none when the flag wasn’t used
Attributes always exist on the Namespace object, even if the flag wasn’t used. They’re None or the default value, not missing:
parser.add_argument('--name')
args = parser.parse_args([])
print(args.name) # None, not AttributeError
Type errors happen at parse time
If you pass type=int, the error message is cryptic by default. Consider wrapping or using argparse.FileType for files:
# This fails with a clear message:
parser.add_argument('port', type=int)
# $ python script.py abc
# error: argument port: invalid int value: 'abc'
Combining nargs and default
With nargs='*' or nargs='+', the default replaces an empty list, not None:
parser.add_argument('--files', nargs='*', default=[])
# --files not provided: args.files = []
# --files a b c: args.files = ['a', 'b', 'c']
See also
- /guides/subprocess-guide/ , run external commands from Python
- /guides/env-variables/ , handle environment variables in CLIs
- /guides/argparse-guide/ , deeper dive into argparse patterns
Where to go next
These argparse basics get you a working CLI. For deeper coverage, see the argparse guide for subparsers and groups, the Click framework tutorial for a friendlier third-party option, and the Typer framework tutorial for argparse-on-type-hints.