Skip to content

Instantly share code, notes, and snippets.

@amarao
Last active May 3, 2024 15:54
Show Gist options
  • Star 53 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save amarao/36327a6f77b86b90c2bca72ba03c9d3a to your computer and use it in GitHub Desktop.
Save amarao/36327a6f77b86b90c2bca72ba03c9d3a to your computer and use it in GitHub Desktop.
Example of argparse with subparsers for python
#!/usr/bin/env python
import argparse
def main(command_line=None):
parser = argparse.ArgumentParser('Blame Praise app')
parser.add_argument(
'--debug',
action='store_true',
help='Print debug info'
)
subparsers = parser.add_subparsers(dest='command')
blame = subparsers.add_parser('blame', help='blame people')
blame.add_argument(
'--dry-run',
help='do not blame, just pretend',
action='store_true'
)
blame.add_argument('name', nargs='+', help='name(s) to blame')
praise = subparsers.add_parser('praise', help='praise someone')
praise.add_argument('name', help='name of person to praise')
praise.add_argument(
'reason',
help='what to praise for (optional)',
default="no reason",
nargs='?'
)
args = parser.parse_args(command_line)
if args.debug:
print("debug: " + str(args))
if args.command == 'blame':
if args.dry_run:
print("Not for real")
print("blaming " + ", ".join(args.name))
elif args.command == 'praise':
print('praising ' + args.name + ' for ' + args.reason)
if __name__ == '__main__':
main()
@hossein-rafipoor
Copy link

It would be helpful if you could add some command-line examples of using the command.

@dmtmov
Copy link

dmtmov commented Nov 23, 2020

Thanks for examples!
The usage:

$ python blame-praise.py --help
$ python blame-praise.py blame --help
$ python blame-praise.py praise --help

@Atlas48
Copy link

Atlas48 commented Sep 11, 2021

typo: "subparsers" is misspelled as subprasers.

@amarao
Copy link
Author

amarao commented Sep 12, 2021

fixed

@avatar-lavventura
Copy link

@amarao Thanks this is helpful

@andreburto
Copy link

Thank you! This example was amazing.

@dmailman01
Copy link

This is great - was trying to figure out a way to make a general command-line tool for work and subparser commands fit what I was wanting to do.

Question though - command_line is defined in the function arguments? I'm unsure how this actually works so I'm going to write as I try to think through it. The main() function argument is =None, but this is setting its default value right? And command_line is actually getting set to the arguments given on the command line? How does that work?

Thanks in advance! I just want to solidify my understanding here as much as possible.

@luhahn
Copy link

luhahn commented Jun 9, 2022

There might be a more elegant solution for subparsers by using set_defaults to directly run the desired function:

import argparse

class Test:
  def __init__(self) -> None:
    pass

  def foo(self, args):
    print("foo")

  def bar(self, args):
    print("bar {}".format(args.context))

def main():
  test = Test()

  parser = argparse.ArgumentParser(description='Foo Bar')
  subparsers = parser.add_subparsers(dest='command', help='Commands to run', required=True)

  foo = subparsers.add_parser('foo', help='foo some Foos')
  foo.set_defaults(func=test.foo)

  bar = subparsers.add_parser('bar', help='bar some Bars')
  bar.add_argument('context', help='context for bar')
  bar.set_defaults(func=test.bar)

  args = parser.parse_args()
  args.func(args)

if __name__ == '__main__':
  main()

edited to include @Cinndy s comment.
Also added required=True to subparser, since there is a known python3 argparse bug, which gives a misleading error message, if you don't provide any arguments.

Traceback (most recent call last):
  File "/home/rdo/workspace/repos/test.py", line 30, in <module>
    main()
  File "/home/rdo/workspace/repos/test.py", line 27, in main
    args.func(args)
AttributeError: 'Namespace' object has no attribute 'func'

@Cinndy
Copy link

Cinndy commented Jul 11, 2022

@luhahn this is great, thank you! One small thing: should the foo definition be foo(self, args)? Thanks again!

@luhahn
Copy link

luhahn commented Jul 13, 2022

@Cinndy indeed, i missed that :)

@fireattack
Copy link

fireattack commented Jul 19, 2022

@luhahn a small thing I don't quite like about this pattern is that it makes all the actual arguments hidden inside Namespace args instead being explicit. Which also makes it kinda difficult to "retrofit" argparse into a script that has batch of utility functions.

I prefer to keep my actual functions in format similar def func(url, headers=None).

This is what I come up with, I know it's not as pretty but works for me. Any input would be appreciated.

import argparse

def minus_one(x):
  print(int(x) - 1)

def time(x, y):
  print(int(x) * int(y))

def main():
  parser = argparse.ArgumentParser(description='Foo Bar')
  subparsers = parser.add_subparsers(dest='command', help='Commands to run', required=True)

  parser_minus_one = subparsers.add_parser('minusone')
  parser_minus_one.add_argument('x', help='X')
  parser_minus_one.set_defaults(func=minus_one)

  parser_time = subparsers.add_parser('time', help='X times Y')
  parser_time.add_argument('x', help='X')
  parser_time.add_argument('y', help='Y')
  parser_time.set_defaults(func=time)

  args = parser.parse_args()
  args_ = vars(args).copy()
  args_.pop('command', None)
  args_.pop('func', None)
  args.func(**args_)

if __name__ == '__main__':
  main()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment