Some argument behaviours can't be expressed with type and the built-in actions alone.
An action is an object called each time its argument appears on the command line. Subclass argparse.Action and implement __call__:
class KeyValue(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
...
setattr(namespace, self.dest, result)The arguments:
parser: the parser, so you can callparser.error(...),namespace: the result object being filled in; read and writeself.deston it,values: the already type-converted value(s), a list whennargsis set,option_string: which flag was used (Nonefor positionals).
__init__ receives the add_argument keywords (option_strings, dest, nargs, default...); override it to validate your options or fix nargs, and call super().__init__.
Important behaviours:
- Actions run in command-line order, only when the argument appears. Defaults are set beforehand, not through your action.
- Accumulating actions must copy before mutating: a list default is shared, so
items = list(getattr(namespace, self.dest) or []). - Since 3.9 an action may define
format_usage();BooleanOptionalActionuses it to show--x | --no-x.
Prefer a type= function when you only convert single values; use an Action when behaviour depends on state across occurrences.
Example
import argparse
class KeyValue(argparse.Action):
"""Collect --set KEY=VALUE pairs into a dict."""
def __call__(self, parser, namespace, values, option_string=None):
if "=" not in values:
parser.error(f"{option_string} expects KEY=VALUE, got {values!r}")
key, value = values.split("=", 1)
current = dict(getattr(namespace, self.dest) or {}) # copy, don't mutate default
current[key] = value
setattr(namespace, self.dest, current)
p = argparse.ArgumentParser(prog="deploy")
p.add_argument("--set", action=KeyValue, metavar="KEY=VALUE", default={})
a = p.parse_args(["--set", "region=eu", "--set", "replicas=3"])
print(a.set) # {'region': 'eu', 'replicas': '3'}
print(p.parse_args([]).set) # {}
try:
p.parse_args(["--set", "oops"])
except SystemExit:
pass
# deploy: error: --set expects KEY=VALUE, got 'oops'Remember
- Subclass Action and implement __call__(parser, namespace, values, option_string)
- Write results with setattr(namespace, self.dest, ...)
- Copy mutable defaults before changing them
Watch out for
- Mutating the default list or dict in place, so state leaks between parse_args calls.