argparse.Action
The Action class is the base class for all action classes in argparse. Actions define how command-line arguments are processed and stored when they are encountered during parsing. Understanding actions is crucial for creating custom argument behavior.
📚 Quick Reference
Module: argparse
Type: Base Class
Purpose: Define how command-line arguments are processed and stored
Documentation: argparse.Action
Inheritance: Subclass to create custom actions
🔧 Action Base Class
Constructor
argparse.Action(option_strings, dest, nargs=None, const=None, default=None,
type=None, choices=None, required=False, help=None, metavar=None)
Parameters:
option_strings: List of command-line option strings (e.g., ['-f', '--foo'])dest: Attribute name to store parsed valuesnargs: Number of command-line arguments to consumeconst: Constant value for certain action typesdefault: Default value when argument is not presenttype: Function to convert argument string to desired typechoices: Allowed values for the argumentrequired: Whether argument is required (for optional arguments)help: Help message for the argumentmetavar: Name for argument in help messages
Core Methods
__call__(parser, namespace, values, option_string=None)
Called when the action processes an argument.
Parameters:
parser: ArgumentParser instance processing the argumentsnamespace: Namespace object to store parsed valuesvalues: Associated command-line argumentsoption_string: Option string that triggered this action
Must be implemented by subclasses.
🎯 Built-in Action Types
1. Store Actions
store (default)
Store the argument value.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--name', action='store') # Default action
parser.add_argument('--age', type=int) # Implicit store action
args = parser.parse_args(['--name', 'Alice', '--age', '25'])
print(args.name) # 'Alice'
print(args.age) # 25
store_const
Store a constant value.
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', action='store_const', const=True)
parser.add_argument('--quiet', action='store_const', const=True, dest='verbose')
args = parser.parse_args(['--verbose'])
print(args.verbose) # True
args = parser.parse_args(['--quiet'])
print(args.verbose) # True (same destination)
store_true / store_false
Store True/False when the argument is present.
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', action='store_true')
parser.add_argument('--quiet', action='store_false')
args = parser.parse_args(['--verbose'])
print(args.verbose) # True
print(args.quiet) # False (default)
args = parser.parse_args(['--quiet'])
print(args.verbose) # False (default)
print(args.quiet) # False (explicit)
2. Append Actions
append
Append values to a list.
parser = argparse.ArgumentParser()
parser.add_argument('--item', action='append')
args = parser.parse_args(['--item', 'a', '--item', 'b', '--item', 'c'])
print(args.item) # ['a', 'b', 'c']
append_const
Append a constant value to a list.
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', action='append_const', const='v', dest='flags')
parser.add_argument('--debug', action='append_const', const='d', dest='flags')
args = parser.parse_args(['--verbose', '--debug', '--verbose'])
print(args.flags) # ['v', 'd', 'v']
3. Count Action
count
Count the number of times an argument appears.
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose', action='count', default=0)
args = parser.parse_args(['-v'])
print(args.verbose) # 1
args = parser.parse_args(['-vvv'])
print(args.verbose) # 3
args = parser.parse_args(['--verbose', '--verbose'])
print(args.verbose) # 2
4. Special Actions
help
Show help message and exit.
parser = argparse.ArgumentParser()
parser.add_argument('-h', '--help', action='help') # Usually automatic
version
Show version information and exit.
parser = argparse.ArgumentParser()
parser.add_argument('--version', action='version', version='MyApp 1.0')
# Running with --version prints "MyApp 1.0" and exits
extend (Python 3.8+)
Extend a list with multiple values.
parser = argparse.ArgumentParser()
parser.add_argument('--items', action='extend', nargs='+')
args = parser.parse_args(['--items', 'a', 'b', '--items', 'c', 'd'])
print(args.items) # ['a', 'b', 'c', 'd']
🛠️ Custom Actions
Creating Custom Actions
import argparse
class ValidateEmailAction(argparse.Action):
"""Custom action to validate email addresses."""
def __call__(self, parser, namespace, values, option_string=None):
import re
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if isinstance(values, list):
# Handle multiple values
for email in values:
if not re.match(email_pattern, email):
parser.error(f"Invalid email address: {email}")
setattr(namespace, self.dest, values)
else:
# Handle single value
if not re.match(email_pattern, values):
parser.error(f"Invalid email address: {values}")
setattr(namespace, self.dest, values)
# Usage
parser = argparse.ArgumentParser()
parser.add_argument('--email', action=ValidateEmailAction)
parser.add_argument('--emails', action=ValidateEmailAction, nargs='+')
# Valid usage
args = parser.parse_args(['--email', 'user@example.com'])
print(args.email) # user@example.com
# Invalid usage would raise an error
# args = parser.parse_args(['--email', 'invalid-email'])
File Validation Action
import os
import argparse
class ValidateFileAction(argparse.Action):
"""Custom action to validate file existence and permissions."""
def __init__(self, option_strings, dest, **kwargs):
self.must_exist = kwargs.pop('must_exist', True)
self.readable = kwargs.pop('readable', False)
self.writable = kwargs.pop('writable', False)
super().__init__(option_strings, dest, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
filepath = values
if self.must_exist and not os.path.exists(filepath):
parser.error(f"File does not exist: {filepath}")
if os.path.exists(filepath):
if self.readable and not os.access(filepath, os.R_OK):
parser.error(f"File is not readable: {filepath}")
if self.writable and not os.access(filepath, os.W_OK):
parser.error(f"File is not writable: {filepath}")
setattr(namespace, self.dest, filepath)
# Usage
parser = argparse.ArgumentParser()
parser.add_argument('--input', action=ValidateFileAction,
must_exist=True, readable=True)
parser.add_argument('--output', action=ValidateFileAction,
must_exist=False, writable=True)
args = parser.parse_args(['--input', 'existing_file.txt',
'--output', 'output_file.txt'])
Range Validation Action
class RangeAction(argparse.Action):
"""Custom action to validate numeric ranges."""
def __init__(self, option_strings, dest, min_val=None, max_val=None, **kwargs):
self.min_val = min_val
self.max_val = max_val
super().__init__(option_strings, dest, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
try:
# Convert to appropriate numeric type
if self.type:
value = self.type(values)
else:
# Try int first, then float
try:
value = int(values)
except ValueError:
value = float(values)
# Validate range
if self.min_val is not None and value < self.min_val:
parser.error(f"{option_string} must be >= {self.min_val}")
if self.max_val is not None and value > self.max_val:
parser.error(f"{option_string} must be <= {self.max_val}")
setattr(namespace, self.dest, value)
except ValueError:
parser.error(f"{option_string} must be a number")
# Usage
parser = argparse.ArgumentParser()
parser.add_argument('--port', action=RangeAction, min_val=1, max_val=65535)
parser.add_argument('--percentage', action=RangeAction, min_val=0.0, max_val=100.0)
args = parser.parse_args(['--port', '8080', '--percentage', '75.5'])
print(args.port) # 8080
print(args.percentage) # 75.5
Multiple Choice Action
class MultipleChoiceAction(argparse.Action):
"""Action that allows multiple selections from a set of choices."""
def __init__(self, option_strings, dest, choices=None, **kwargs):
if choices is None:
choices = []
self.available_choices = set(choices)
kwargs['choices'] = None # Don't let argparse handle choices
super().__init__(option_strings, dest, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
if isinstance(values, str):
values = [values]
# Validate all choices
invalid_choices = set(values) - self.available_choices
if invalid_choices:
parser.error(f"Invalid choices: {invalid_choices}. "
f"Choose from: {self.available_choices}")
# Store unique values
current_values = getattr(namespace, self.dest, []) or []
current_values.extend(values)
setattr(namespace, self.dest, list(set(current_values)))
# Usage
parser = argparse.ArgumentParser()
parser.add_argument('--features', action=MultipleChoiceAction,
choices=['auth', 'cache', 'logging', 'metrics'],
help='Enable features (can be specified multiple times)')
args = parser.parse_args(['--features', 'auth', '--features', 'cache'])
print(args.features) # ['auth', 'cache']
🎯 Advanced Action Patterns
Conditional Actions
class ConditionalAction(argparse.Action):
"""Action that behaves differently based on other arguments."""
def __init__(self, option_strings, dest, condition_arg=None, **kwargs):
self.condition_arg = condition_arg
super().__init__(option_strings, dest, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
# Check condition
condition_value = getattr(namespace, self.condition_arg, None)
if condition_value == 'verbose':
# Store detailed information
setattr(namespace, self.dest, f"Detailed: {values}")
elif condition_value == 'quiet':
# Store minimal information
setattr(namespace, self.dest, values[:10] + "...")
else:
# Default behavior
setattr(namespace, self.dest, values)
# Usage
parser = argparse.ArgumentParser()
parser.add_argument('--mode', choices=['verbose', 'quiet', 'normal'])
parser.add_argument('--message', action=ConditionalAction,
condition_arg='mode')
args = parser.parse_args(['--mode', 'verbose', '--message', 'Hello World'])
print(args.message) # "Detailed: Hello World"
Accumulating Actions
class KeyValueAction(argparse.Action):
"""Action to parse key=value pairs into a dictionary."""
def __call__(self, parser, namespace, values, option_string=None):
# Initialize dictionary if needed
if not hasattr(namespace, self.dest) or getattr(namespace, self.dest) is None:
setattr(namespace, self.dest, {})
# Parse key=value
try:
key, value = values.split('=', 1)
getattr(namespace, self.dest)[key] = value
except ValueError:
parser.error(f"'{values}' is not in key=value format")
# Usage
parser = argparse.ArgumentParser()
parser.add_argument('-D', '--define', action=KeyValueAction,
help='Define key=value pairs')
args = parser.parse_args(['-D', 'name=Alice', '-D', 'age=25', '-D', 'city=NYC'])
print(args.define) # {'name': 'Alice', 'age': '25', 'city': 'NYC'}
🎯 Practical Use Cases
1. Configuration File Action
import json
import argparse
class ConfigFileAction(argparse.Action):
"""Load configuration from JSON file."""
def __call__(self, parser, namespace, values, option_string=None):
try:
with open(values, 'r') as f:
config = json.load(f)
# Merge config into namespace
for key, value in config.items():
setattr(namespace, key, value)
# Store config file path
setattr(namespace, self.dest, values)
except FileNotFoundError:
parser.error(f"Config file not found: {values}")
except json.JSONDecodeError as e:
parser.error(f"Invalid JSON in config file: {e}")
# Usage
parser = argparse.ArgumentParser()
parser.add_argument('--config', action=ConfigFileAction)
parser.add_argument('--name', default='DefaultName')
parser.add_argument('--port', type=int, default=8080)
# With config.json: {"name": "MyApp", "port": 3000}
args = parser.parse_args(['--config', 'config.json'])
print(args.name) # "MyApp" (from config)
print(args.port) # 3000 (from config)
print(args.config) # "config.json"
2. Environment Variable Action
import os
class EnvVarAction(argparse.Action):
"""Use environment variable as default value."""
def __init__(self, option_strings, dest, env_var=None, **kwargs):
self.env_var = env_var or dest.upper()
# Set default from environment variable
if self.env_var in os.environ:
kwargs['default'] = os.environ[self.env_var]
super().__init__(option_strings, dest, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values)
# Usage
parser = argparse.ArgumentParser()
parser.add_argument('--host', action=EnvVarAction, env_var='SERVER_HOST',
default='localhost')
parser.add_argument('--port', action=EnvVarAction, env_var='SERVER_PORT',
type=int, default=8080)
# Environment variables: SERVER_HOST=example.com, SERVER_PORT=3000
args = parser.parse_args([])
print(args.host) # "example.com" (from env var)
print(args.port) # 3000 (from env var)
# Command line overrides environment
args = parser.parse_args(['--host', 'override.com'])
print(args.host) # "override.com" (from command line)
3. Validation and Transformation Action
import re
from urllib.parse import urlparse
class URLAction(argparse.Action):
"""Validate and normalize URL arguments."""
def __call__(self, parser, namespace, values, option_string=None):
# Add protocol if missing
if not values.startswith(('http://', 'https://')):
values = 'https://' + values
# Parse and validate URL
try:
parsed = urlparse(values)
if not all([parsed.scheme, parsed.netloc]):
raise ValueError("Invalid URL structure")
# Normalize URL
normalized_url = parsed.geturl()
setattr(namespace, self.dest, normalized_url)
except Exception as e:
parser.error(f"Invalid URL '{values}': {e}")
# Usage
parser = argparse.ArgumentParser()
parser.add_argument('--url', action=URLAction)
args = parser.parse_args(['--url', 'example.com/path'])
print(args.url) # "https://example.com/path"
args = parser.parse_args(['--url', 'http://example.com'])
print(args.url) # "http://example.com"
🚀 Performance Considerations
Lazy Validation
class LazyValidationAction(argparse.Action):
"""Defer expensive validation until all arguments are parsed."""
def __init__(self, option_strings, dest, validator=None, **kwargs):
self.validator = validator
super().__init__(option_strings, dest, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
# Store raw value for now
setattr(namespace, self.dest, values)
# Store validator for later execution
if not hasattr(namespace, '_validators'):
namespace._validators = []
namespace._validators.append((self.dest, self.validator, values))
def validate_deferred(namespace):
"""Run deferred validations."""
if not hasattr(namespace, '_validators'):
return
for dest, validator, value in namespace._validators:
if validator and not validator(value):
raise ValueError(f"Validation failed for {dest}: {value}")
# Usage example with expensive validation
def expensive_validator(value):
# Simulate expensive operation
import time
time.sleep(0.1)
return len(value) > 5
parser = argparse.ArgumentParser()
parser.add_argument('--name', action=LazyValidationAction,
validator=expensive_validator)
args = parser.parse_args(['--name', 'Alice'])
# Validation happens here
validate_deferred(args)
🔍 Testing Custom Actions
import unittest
import argparse
from io import StringIO
import sys
class TestCustomActions(unittest.TestCase):
"""Test custom argparse actions."""
def test_email_validation_action(self):
"""Test email validation action."""
parser = argparse.ArgumentParser()
parser.add_argument('--email', action=ValidateEmailAction)
# Valid email
args = parser.parse_args(['--email', 'test@example.com'])
self.assertEqual(args.email, 'test@example.com')
# Invalid email should raise SystemExit
with self.assertRaises(SystemExit):
parser.parse_args(['--email', 'invalid-email'])
def test_range_action(self):
"""Test range validation action."""
parser = argparse.ArgumentParser()
parser.add_argument('--port', action=RangeAction,
min_val=1, max_val=65535)
# Valid port
args = parser.parse_args(['--port', '8080'])
self.assertEqual(args.port, 8080)
# Invalid port (too high)
with self.assertRaises(SystemExit):
parser.parse_args(['--port', '99999'])
def test_key_value_action(self):
"""Test key-value parsing action."""
parser = argparse.ArgumentParser()
parser.add_argument('-D', action=KeyValueAction)
args = parser.parse_args(['-D', 'key=value'])
self.assertEqual(args.D, {'key': 'value'})
if __name__ == '__main__':
unittest.main()
🔗 Related Components
- argparse.ArgumentParser - Main parser class that uses actions
- argparse.Namespace - Container for parsed argument values
- argparse.FileType - Built-in action for file handling
📚 Additional Resources
- argparse.Action documentation
- Python argparse tutorial
- argparse action examples
- Custom argparse actions guide
Custom actions provide powerful ways to extend argparse functionality, enabling sophisticated argument validation, transformation, and processing that goes beyond the built-in action types.