How to handle variable number of arguments (nargs='*')

Learn how to handle variable number of arguments (nargs='*') with practical examples, diagrams, and best practices. Covers python, argparse development techniques with visual explanations.

Mastering Variable Arguments with argparse: The 'nargs='*' Approach

Hero image for How to handle variable number of arguments (nargs='*')

Learn how to effectively handle an arbitrary number of command-line arguments in Python using argparse's nargs='*' feature, making your scripts flexible and robust.

When developing command-line tools in Python, you often encounter scenarios where your script needs to accept a variable number of arguments. For instance, a file processing utility might take one or more filenames, or a data analysis script could accept an arbitrary list of values. The argparse module, Python's recommended command-line parsing library, provides a powerful mechanism to handle such cases: the nargs='*' option.

Understanding nargs='*'

The nargs argument in parser.add_argument() specifies how many command-line arguments should be consumed. When you set nargs='*', it instructs argparse to collect zero or more arguments into a list. This is incredibly useful for arguments that are optional and can appear multiple times, such as a list of files, names, or numerical inputs.

flowchart TD
    A[Script Execution] --> B{Parse Arguments}
    B --> C{Argument 'files' defined with nargs='*'}
    C --> D{User provides 0 files?}
    D -- Yes --> E[files = [] (empty list)]
    D -- No --> F{User provides 1+ files?}
    F -- Yes --> G[files = [file1, file2, ...] (list of strings)]
    G --> H[Process 'files' list]
    E --> H

Flowchart illustrating how nargs='*' handles zero or more arguments.

When nargs='*' is used, the parsed argument will always be a list, even if no arguments are provided (in which case it will be an empty list) or if only one argument is provided (it will be a list containing that single argument). This consistency simplifies your code, as you can always iterate over the argument's value.

import argparse

parser = argparse.ArgumentParser(description='Process a variable number of items.')
parser.add_argument(
    'items',
    metavar='ITEM',
    type=str,
    nargs='*',
    help='An item to be processed (can be multiple)'
)

args = parser.parse_args()

if args.items:
    print(f"Processing {len(args.items)} items: {args.items}")
    for item in args.items:
        print(f"  - {item}")
else:
    print("No items provided for processing.")

Basic example of using nargs='*' to accept multiple items.

Combining with Other Arguments

You can combine nargs='*' with other types of arguments, including required positional arguments and optional arguments. However, there are some important considerations regarding positional arguments.

Typically, nargs='*' is best used for the last positional argument in your parser definition. If you place a positional argument with nargs='*' before other positional arguments, argparse might become ambiguous about which arguments belong to the nargs='*' list and which belong to the subsequent positional arguments. While argparse has rules to handle this, it can lead to unexpected parsing behavior and is generally discouraged for clarity.

import argparse

parser = argparse.ArgumentParser(description='Process files with an output directory.')
parser.add_argument(
    '--output', '-o',
    type=str,
    default='.',
    help='Specify an output directory'
)
parser.add_argument(
    'files',
    metavar='FILE',
    type=str,
    nargs='*',
    help='List of files to process'
)

args = parser.parse_args()

print(f"Output directory: {args.output}")
if args.files:
    print(f"Files to process: {args.files}")
else:
    print("No files specified.")

Example combining nargs='*' with an optional argument.

Practical Use Cases and Best Practices

The nargs='*' option shines in various practical scenarios:

1. File Processing Utilities

Accepting one or more filenames for operations like copying, deleting, or analyzing. Users can specify script.py file1.txt file2.txt or just script.py to operate on a default set.

2. Data Input Lists

Providing a list of values (e.g., numbers, IDs) to a script. For example, script.py --data 10 20 30.

3. Flexible Command Structure

When your script acts as a wrapper for another tool and needs to pass through an arbitrary number of arguments to that underlying tool.

When using nargs='*', always check if the resulting list is empty before attempting to process its contents. This prevents errors if the user doesn't provide any arguments for that specific option.

import argparse

parser = argparse.ArgumentParser(description='A script that can take multiple inputs.')
parser.add_argument(
    '--names',
    type=str,
    nargs='*',
    default=['Guest'], # Provide a default if no names are given
    help='List of names to greet'
)

args = parser.parse_args()

print("Greeting people:")
for name in args.names:
    print(f"Hello, {name}!")

Using nargs='*' with a default value for a list.

In the example above, if no --names are provided, args.names will default to ['Guest'], ensuring that the greeting loop always has something to process. This is a common pattern to provide sensible defaults for variable arguments.