Home

run(**vars(args))

David Priver, July 27th, 2022

Python has the pretty decent argument parsing module argparse as part of the standard library. It offers a procedural API for building up a parser for a program's command line arguments. After calling parse_args, you get back a simple namespace object that contains the values as defined by your parser (which I'll refer to as args.)

# some_script.py
import argparse

def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument('N', type=int)
    parser.add_argument('pid', type=int)
    parser.add_argument('--with-candles', action='store_true')

    args = parser.parse_args()
    ...

if __name__ == '__main__':
    main()

There's just one problem: the only way to tell what is in this returned args is by reading the code that builds up the parser.

What I've found to be an effective way to help with that problem is to immediately grab the attributes off of the args and pass them to a function that actually does the work (normally named run). You want to spend as little time as possible with this dynamic, unstructured args.

# some_script.py
import argparse

def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument('N', type=int)
    parser.add_argument('pid', type=int)
    parser.add_argument('--with-candles', action='store_true')

    args = parser.parse_args()
    run(args.N, args.pid, args.with_candles)


def run(N:int, pid:int, with_candles:bool=False) -> None:
    ...

if __name__ == '__main__':
    main()

Having a separate run function is nice anyway, as it means you can write scripts that import your script and just call the run function:

# myscript.py
import some_script
import another_script

some_script.run(1, 2, with_candles=True)
another_script.run('big', 1, 'hello.txt')

However, this run function poses its own problem. It's tedious to grab all of the attributes off of args, which tempts you to the dark side of just passing the args object around in your application, making it very hard to understand what's going on.

Luckily, there is a relatively obscure builtin function in python called vars. vars (when given an argument) basically grabs all of the attributes off of an object and puts them into a dict. Coupling that with python's convenient ** for dictionary unpacking, you are saved the trouble of manually peeling the attributes off yourself. Thus the title of this article: you use the incantation run(**vars(args)) immediately after parsing command-line arguments.

# some_script.py
import argparse

def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument('N', type=int)
    parser.add_argument('pid', type=int)
    parser.add_argument('--with-candles', action='store_true')

    args = parser.parse_args()
    run(**vars(args)) # Here it is.

def run(N:int, pid:int, with_candles:bool=False) -> None:
    ...

if __name__ == '__main__':
    main()

Relatively simple pattern, but I've found it to be useful. I'm sure there are argument parsing libraries that do nicer things, but with this pattern you can stick to the standard library which lessens the dependency problem.

All code in this article is released into the public domain.