Comment

Thomas Hess

One could do so.
Imho, the post reaches a flawed solution. I personally just keep the Namespace object as-is and pass it as an argument. This keeps everything together, reduces argument count and potential for mixing stuff. This also avoids the unwanted duplication your solution tries to avoid.

Here's what I always do:

```python

import argparse
import typing

class Namespace(typing.NamedTuple):
    """Mock Namespace used for type hinting purposes. Keep in sync with the argument parser defined in parse_args"""
    option_a: bool
    option_b: int

def parse_args() -> Namespace:
    parser = argparse.ArgumentParser()
    parser.add_argument("-o", "--option-a", action="store_true")
    parser.add_argument("-n", "--option-b", type=int, default=0)
    return parser.parse_args()

def main(args: Namespace):
    if args.option_a:
        print(args.option_b)
    some_logic(args)

def some_logic(args: Namespace):
    pass

if __name__ == "__main__":
    args = parse_args()
    main(args)

```

With that approach, the IDE/linter can do static type checking, and complains when assigning to the namespace, or when types don't match, or when accessed arguments don't exist.

You have to write a unit test though, that ensures that the mock Namespace is in sync with the actual one created by the argument parser. But that's easy enough, just call parse_args() in a test and compare the result keys/value types with the Namespace.__annotations__ dict.

One thing to keep in mind is that the mock Namespace is not the actual Namespace class. So you cannot do checks like `isinstance(args, Namespace)`. But those shouldn't be done in the first place, anyway.