Python
argparse
dictionary
programming
tutorial

What is the right way to treat Python argparse.Namespace as a dictionary?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Python's argparse.Namespace stores parsed command-line arguments as attributes (accessed with dot notation like args.verbose). To treat it as a dictionary, use vars(args), which returns the __dict__ of the namespace object. This is the official, documented way to convert between the two representations. The returned dictionary is the actual internal storage — modifying it also modifies the namespace, and vice versa.

Basic Conversion with vars()

python
1import argparse
2
3parser = argparse.ArgumentParser()
4parser.add_argument('--name', default='World')
5parser.add_argument('--count', type=int, default=1)
6parser.add_argument('--verbose', action='store_true')
7
8args = parser.parse_args(['--name', 'Alice', '--count', '3'])
9
10# Convert to dictionary
11args_dict = vars(args)
12print(args_dict)
13# {'name': 'Alice', 'count': 3, 'verbose': False}
14
15# Access as dictionary
16print(args_dict['name'])  # Alice
17
18# Access as attribute (still works)
19print(args.name)          # Alice

vars() Returns a Live Reference

The dictionary from vars(args) is the same object as args.__dict__. Changes to the dictionary affect the namespace and vice versa:

python
1args_dict = vars(args)
2
3# Modify via dictionary
4args_dict['name'] = 'Bob'
5print(args.name)  # Bob — namespace updated too
6
7# Modify via attribute
8args.count = 10
9print(args_dict['count'])  # 10 — dict updated too
10
11# To get an independent copy:
12args_copy = dict(vars(args))
13# or
14import copy
15args_copy = copy.copy(vars(args))

Why Not Use dict Directly?

vars(args) and args.__dict__ return the same object, but vars() is the Pythonic way:

python
1# Both work, but vars() is preferred
2d1 = vars(args)
3d2 = args.__dict__
4
5assert d1 is d2  # Same object
6
7# vars() is recommended by the argparse documentation
8# __dict__ is an implementation detail that happens to work

Practical Use Cases

Pass Arguments to a Function as kwargs

python
1def configure(name, count, verbose):
2    print(f"Configuring {name} with count={count}, verbose={verbose}")
3
4args = parser.parse_args()
5configure(**vars(args))
6# Unpacks: configure(name='Alice', count=3, verbose=False)

Merge with Default Configuration

python
1defaults = {
2    'name': 'default',
3    'count': 1,
4    'verbose': False,
5    'output': 'result.txt',  # Not an argparse argument
6}
7
8# CLI args override defaults
9config = {**defaults, **vars(args)}
10print(config)
11# {'name': 'Alice', 'count': 3, 'verbose': False, 'output': 'result.txt'}

Serialize to JSON

python
1import json
2
3args = parser.parse_args()
4config_json = json.dumps(vars(args), indent=2)
5print(config_json)
6# {
7#   "name": "Alice",
8#   "count": 3,
9#   "verbose": false
10# }
11
12# Save to file
13with open('config.json', 'w') as f:
14    json.dump(vars(args), f, indent=2)

Filter Arguments

python
1args = parser.parse_args()
2args_dict = vars(args)
3
4# Only non-default arguments
5non_default = {k: v for k, v in args_dict.items() if v is not None}
6
7# Only string arguments
8string_args = {k: v for k, v in args_dict.items() if isinstance(v, str)}

Convert Back to Namespace

python
1# Dictionary to Namespace
2config = {'name': 'Alice', 'count': 3, 'verbose': True}
3args = argparse.Namespace(**config)
4
5print(args.name)     # Alice
6print(args.verbose)  # True

Working with Subparsers

python
1parser = argparse.ArgumentParser()
2subparsers = parser.add_subparsers(dest='command')
3
4run_parser = subparsers.add_parser('run')
5run_parser.add_argument('--fast', action='store_true')
6
7test_parser = subparsers.add_parser('test')
8test_parser.add_argument('--coverage', action='store_true')
9
10args = parser.parse_args(['run', '--fast'])
11print(vars(args))
12# {'command': 'run', 'fast': True}

Checking if an Argument Was Provided

python
1parser = argparse.ArgumentParser()
2parser.add_argument('--port', type=int, default=None)
3args = parser.parse_args()
4
5# Check via dictionary
6if 'port' in vars(args) and vars(args)['port'] is not None:
7    print(f"Using port {args.port}")
8
9# Simpler attribute check
10if args.port is not None:
11    print(f"Using port {args.port}")

Common Pitfalls

  • Mutating vars(args) mutates the namespace: The dictionary is a live reference, not a copy. If you need an independent dictionary, use dict(vars(args)) to create a shallow copy.
  • Non-serializable argument types: vars(args) may contain types that json.dumps cannot handle (e.g., pathlib.Path, file objects from type=argparse.FileType). Convert these before serialization.
  • Using **vars(args) with functions that have extra parameters: If the function accepts **kwargs, all arguments pass through. If it has fixed parameters, extra argparse arguments raise TypeError: unexpected keyword argument.
  • None vs not provided: argparse uses None as the default for optional arguments. There is no built-in way to distinguish "user passed --port None" from "user did not pass --port". Use a sentinel default like argparse.SUPPRESS or a custom default object.
  • Namespace comparison gotcha: Two Namespace objects with the same attributes are equal (==), but converting to dict and comparing is safer when attributes might have been dynamically added.

Summary

  • Use vars(args) to convert argparse.Namespace to a dictionary — this is the official approach
  • The returned dictionary is a live reference to the namespace's __dict__, not a copy
  • Use **vars(args) to unpack arguments as keyword arguments to functions
  • Use argparse.Namespace(**dict) to convert a dictionary back to a namespace
  • Create a copy with dict(vars(args)) if you need to modify the dictionary without affecting the namespace

Course illustration
Course illustration

All Rights Reserved.