1*****************
2Argparse Tutorial
3*****************
4
5:author: Tshepang Lekhonkhobe
6
7.. _argparse-tutorial:
8
9This tutorial is intended to be a gentle introduction to :mod:`argparse`, the
10recommended command-line parsing module in the Python standard library.
11
12.. note::
13
14   There are two other modules that fulfill the same task, namely
15   :mod:`getopt` (an equivalent for :c:func:`getopt` from the C
16   language) and the deprecated :mod:`optparse`.
17   Note also that :mod:`argparse` is based on :mod:`optparse`,
18   and therefore very similar in terms of usage.
19
20
21Concepts
22========
23
24Let's show the sort of functionality that we are going to explore in this
25introductory tutorial by making use of the :command:`ls` command:
26
27.. code-block:: shell-session
28
29   $ ls
30   cpython  devguide  prog.py  pypy  rm-unused-function.patch
31   $ ls pypy
32   ctypes_configure  demo  dotviewer  include  lib_pypy  lib-python ...
33   $ ls -l
34   total 20
35   drwxr-xr-x 19 wena wena 4096 Feb 18 18:51 cpython
36   drwxr-xr-x  4 wena wena 4096 Feb  8 12:04 devguide
37   -rwxr-xr-x  1 wena wena  535 Feb 19 00:05 prog.py
38   drwxr-xr-x 14 wena wena 4096 Feb  7 00:59 pypy
39   -rw-r--r--  1 wena wena  741 Feb 18 01:01 rm-unused-function.patch
40   $ ls --help
41   Usage: ls [OPTION]... [FILE]...
42   List information about the FILEs (the current directory by default).
43   Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.
44   ...
45
46A few concepts we can learn from the four commands:
47
48* The :command:`ls` command is useful when run without any options at all. It defaults
49  to displaying the contents of the current directory.
50
51* If we want beyond what it provides by default, we tell it a bit more. In
52  this case, we want it to display a different directory, ``pypy``.
53  What we did is specify what is known as a positional argument. It's named so
54  because the program should know what to do with the value, solely based on
55  where it appears on the command line. This concept is more relevant
56  to a command like :command:`cp`, whose most basic usage is ``cp SRC DEST``.
57  The first position is *what you want copied,* and the second
58  position is *where you want it copied to*.
59
60* Now, say we want to change behaviour of the program. In our example,
61  we display more info for each file instead of just showing the file names.
62  The ``-l`` in that case is known as an optional argument.
63
64* That's a snippet of the help text. It's very useful in that you can
65  come across a program you have never used before, and can figure out
66  how it works simply by reading its help text.
67
68
69The basics
70==========
71
72Let us start with a very simple example which does (almost) nothing::
73
74   import argparse
75   parser = argparse.ArgumentParser()
76   parser.parse_args()
77
78Following is a result of running the code:
79
80.. code-block:: shell-session
81
82   $ python3 prog.py
83   $ python3 prog.py --help
84   usage: prog.py [-h]
85
86   optional arguments:
87     -h, --help  show this help message and exit
88   $ python3 prog.py --verbose
89   usage: prog.py [-h]
90   prog.py: error: unrecognized arguments: --verbose
91   $ python3 prog.py foo
92   usage: prog.py [-h]
93   prog.py: error: unrecognized arguments: foo
94
95Here is what is happening:
96
97* Running the script without any options results in nothing displayed to
98  stdout. Not so useful.
99
100* The second one starts to display the usefulness of the :mod:`argparse`
101  module. We have done almost nothing, but already we get a nice help message.
102
103* The ``--help`` option, which can also be shortened to ``-h``, is the only
104  option we get for free (i.e. no need to specify it). Specifying anything
105  else results in an error. But even then, we do get a useful usage message,
106  also for free.
107
108
109Introducing Positional arguments
110================================
111
112An example::
113
114   import argparse
115   parser = argparse.ArgumentParser()
116   parser.add_argument("echo")
117   args = parser.parse_args()
118   print(args.echo)
119
120And running the code:
121
122.. code-block:: shell-session
123
124   $ python3 prog.py
125   usage: prog.py [-h] echo
126   prog.py: error: the following arguments are required: echo
127   $ python3 prog.py --help
128   usage: prog.py [-h] echo
129
130   positional arguments:
131     echo
132
133   optional arguments:
134     -h, --help  show this help message and exit
135   $ python3 prog.py foo
136   foo
137
138Here is what's happening:
139
140* We've added the :meth:`add_argument` method, which is what we use to specify
141  which command-line options the program is willing to accept. In this case,
142  I've named it ``echo`` so that it's in line with its function.
143
144* Calling our program now requires us to specify an option.
145
146* The :meth:`parse_args` method actually returns some data from the
147  options specified, in this case, ``echo``.
148
149* The variable is some form of 'magic' that :mod:`argparse` performs for free
150  (i.e. no need to specify which variable that value is stored in).
151  You will also notice that its name matches the string argument given
152  to the method, ``echo``.
153
154Note however that, although the help display looks nice and all, it currently
155is not as helpful as it can be. For example we see that we got ``echo`` as a
156positional argument, but we don't know what it does, other than by guessing or
157by reading the source code. So, let's make it a bit more useful::
158
159   import argparse
160   parser = argparse.ArgumentParser()
161   parser.add_argument("echo", help="echo the string you use here")
162   args = parser.parse_args()
163   print(args.echo)
164
165And we get:
166
167.. code-block:: shell-session
168
169   $ python3 prog.py -h
170   usage: prog.py [-h] echo
171
172   positional arguments:
173     echo        echo the string you use here
174
175   optional arguments:
176     -h, --help  show this help message and exit
177
178Now, how about doing something even more useful::
179
180   import argparse
181   parser = argparse.ArgumentParser()
182   parser.add_argument("square", help="display a square of a given number")
183   args = parser.parse_args()
184   print(args.square**2)
185
186Following is a result of running the code:
187
188.. code-block:: shell-session
189
190   $ python3 prog.py 4
191   Traceback (most recent call last):
192     File "prog.py", line 5, in <module>
193       print(args.square**2)
194   TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
195
196That didn't go so well. That's because :mod:`argparse` treats the options we
197give it as strings, unless we tell it otherwise. So, let's tell
198:mod:`argparse` to treat that input as an integer::
199
200   import argparse
201   parser = argparse.ArgumentParser()
202   parser.add_argument("square", help="display a square of a given number",
203                       type=int)
204   args = parser.parse_args()
205   print(args.square**2)
206
207Following is a result of running the code:
208
209.. code-block:: shell-session
210
211   $ python3 prog.py 4
212   16
213   $ python3 prog.py four
214   usage: prog.py [-h] square
215   prog.py: error: argument square: invalid int value: 'four'
216
217That went well. The program now even helpfully quits on bad illegal input
218before proceeding.
219
220
221Introducing Optional arguments
222==============================
223
224So far we have been playing with positional arguments. Let us
225have a look on how to add optional ones::
226
227   import argparse
228   parser = argparse.ArgumentParser()
229   parser.add_argument("--verbosity", help="increase output verbosity")
230   args = parser.parse_args()
231   if args.verbosity:
232       print("verbosity turned on")
233
234And the output:
235
236.. code-block:: shell-session
237
238   $ python3 prog.py --verbosity 1
239   verbosity turned on
240   $ python3 prog.py
241   $ python3 prog.py --help
242   usage: prog.py [-h] [--verbosity VERBOSITY]
243
244   optional arguments:
245     -h, --help            show this help message and exit
246     --verbosity VERBOSITY
247                           increase output verbosity
248   $ python3 prog.py --verbosity
249   usage: prog.py [-h] [--verbosity VERBOSITY]
250   prog.py: error: argument --verbosity: expected one argument
251
252Here is what is happening:
253
254* The program is written so as to display something when ``--verbosity`` is
255  specified and display nothing when not.
256
257* To show that the option is actually optional, there is no error when running
258  the program without it. Note that by default, if an optional argument isn't
259  used, the relevant variable, in this case :attr:`args.verbosity`, is
260  given ``None`` as a value, which is the reason it fails the truth
261  test of the :keyword:`if` statement.
262
263* The help message is a bit different.
264
265* When using the ``--verbosity`` option, one must also specify some value,
266  any value.
267
268The above example accepts arbitrary integer values for ``--verbosity``, but for
269our simple program, only two values are actually useful, ``True`` or ``False``.
270Let's modify the code accordingly::
271
272   import argparse
273   parser = argparse.ArgumentParser()
274   parser.add_argument("--verbose", help="increase output verbosity",
275                       action="store_true")
276   args = parser.parse_args()
277   if args.verbose:
278       print("verbosity turned on")
279
280And the output:
281
282.. code-block:: shell-session
283
284   $ python3 prog.py --verbose
285   verbosity turned on
286   $ python3 prog.py --verbose 1
287   usage: prog.py [-h] [--verbose]
288   prog.py: error: unrecognized arguments: 1
289   $ python3 prog.py --help
290   usage: prog.py [-h] [--verbose]
291
292   optional arguments:
293     -h, --help  show this help message and exit
294     --verbose   increase output verbosity
295
296Here is what is happening:
297
298* The option is now more of a flag than something that requires a value.
299  We even changed the name of the option to match that idea.
300  Note that we now specify a new keyword, ``action``, and give it the value
301  ``"store_true"``. This means that, if the option is specified,
302  assign the value ``True`` to :data:`args.verbose`.
303  Not specifying it implies ``False``.
304
305* It complains when you specify a value, in true spirit of what flags
306  actually are.
307
308* Notice the different help text.
309
310
311Short options
312-------------
313
314If you are familiar with command line usage,
315you will notice that I haven't yet touched on the topic of short
316versions of the options. It's quite simple::
317
318   import argparse
319   parser = argparse.ArgumentParser()
320   parser.add_argument("-v", "--verbose", help="increase output verbosity",
321                       action="store_true")
322   args = parser.parse_args()
323   if args.verbose:
324       print("verbosity turned on")
325
326And here goes:
327
328.. code-block:: shell-session
329
330   $ python3 prog.py -v
331   verbosity turned on
332   $ python3 prog.py --help
333   usage: prog.py [-h] [-v]
334
335   optional arguments:
336     -h, --help     show this help message and exit
337     -v, --verbose  increase output verbosity
338
339Note that the new ability is also reflected in the help text.
340
341
342Combining Positional and Optional arguments
343===========================================
344
345Our program keeps growing in complexity::
346
347   import argparse
348   parser = argparse.ArgumentParser()
349   parser.add_argument("square", type=int,
350                       help="display a square of a given number")
351   parser.add_argument("-v", "--verbose", action="store_true",
352                       help="increase output verbosity")
353   args = parser.parse_args()
354   answer = args.square**2
355   if args.verbose:
356       print("the square of {} equals {}".format(args.square, answer))
357   else:
358       print(answer)
359
360And now the output:
361
362.. code-block:: shell-session
363
364   $ python3 prog.py
365   usage: prog.py [-h] [-v] square
366   prog.py: error: the following arguments are required: square
367   $ python3 prog.py 4
368   16
369   $ python3 prog.py 4 --verbose
370   the square of 4 equals 16
371   $ python3 prog.py --verbose 4
372   the square of 4 equals 16
373
374* We've brought back a positional argument, hence the complaint.
375
376* Note that the order does not matter.
377
378How about we give this program of ours back the ability to have
379multiple verbosity values, and actually get to use them::
380
381   import argparse
382   parser = argparse.ArgumentParser()
383   parser.add_argument("square", type=int,
384                       help="display a square of a given number")
385   parser.add_argument("-v", "--verbosity", type=int,
386                       help="increase output verbosity")
387   args = parser.parse_args()
388   answer = args.square**2
389   if args.verbosity == 2:
390       print("the square of {} equals {}".format(args.square, answer))
391   elif args.verbosity == 1:
392       print("{}^2 == {}".format(args.square, answer))
393   else:
394       print(answer)
395
396And the output:
397
398.. code-block:: shell-session
399
400   $ python3 prog.py 4
401   16
402   $ python3 prog.py 4 -v
403   usage: prog.py [-h] [-v VERBOSITY] square
404   prog.py: error: argument -v/--verbosity: expected one argument
405   $ python3 prog.py 4 -v 1
406   4^2 == 16
407   $ python3 prog.py 4 -v 2
408   the square of 4 equals 16
409   $ python3 prog.py 4 -v 3
410   16
411
412These all look good except the last one, which exposes a bug in our program.
413Let's fix it by restricting the values the ``--verbosity`` option can accept::
414
415   import argparse
416   parser = argparse.ArgumentParser()
417   parser.add_argument("square", type=int,
418                       help="display a square of a given number")
419   parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2],
420                       help="increase output verbosity")
421   args = parser.parse_args()
422   answer = args.square**2
423   if args.verbosity == 2:
424       print("the square of {} equals {}".format(args.square, answer))
425   elif args.verbosity == 1:
426       print("{}^2 == {}".format(args.square, answer))
427   else:
428       print(answer)
429
430And the output:
431
432.. code-block:: shell-session
433
434   $ python3 prog.py 4 -v 3
435   usage: prog.py [-h] [-v {0,1,2}] square
436   prog.py: error: argument -v/--verbosity: invalid choice: 3 (choose from 0, 1, 2)
437   $ python3 prog.py 4 -h
438   usage: prog.py [-h] [-v {0,1,2}] square
439
440   positional arguments:
441     square                display a square of a given number
442
443   optional arguments:
444     -h, --help            show this help message and exit
445     -v {0,1,2}, --verbosity {0,1,2}
446                           increase output verbosity
447
448Note that the change also reflects both in the error message as well as the
449help string.
450
451Now, let's use a different approach of playing with verbosity, which is pretty
452common. It also matches the way the CPython executable handles its own
453verbosity argument (check the output of ``python --help``)::
454
455   import argparse
456   parser = argparse.ArgumentParser()
457   parser.add_argument("square", type=int,
458                       help="display the square of a given number")
459   parser.add_argument("-v", "--verbosity", action="count",
460                       help="increase output verbosity")
461   args = parser.parse_args()
462   answer = args.square**2
463   if args.verbosity == 2:
464       print("the square of {} equals {}".format(args.square, answer))
465   elif args.verbosity == 1:
466       print("{}^2 == {}".format(args.square, answer))
467   else:
468       print(answer)
469
470We have introduced another action, "count",
471to count the number of occurrences of a specific optional arguments:
472
473.. code-block:: shell-session
474
475   $ python3 prog.py 4
476   16
477   $ python3 prog.py 4 -v
478   4^2 == 16
479   $ python3 prog.py 4 -vv
480   the square of 4 equals 16
481   $ python3 prog.py 4 --verbosity --verbosity
482   the square of 4 equals 16
483   $ python3 prog.py 4 -v 1
484   usage: prog.py [-h] [-v] square
485   prog.py: error: unrecognized arguments: 1
486   $ python3 prog.py 4 -h
487   usage: prog.py [-h] [-v] square
488
489   positional arguments:
490     square           display a square of a given number
491
492   optional arguments:
493     -h, --help       show this help message and exit
494     -v, --verbosity  increase output verbosity
495   $ python3 prog.py 4 -vvv
496   16
497
498* Yes, it's now more of a flag (similar to ``action="store_true"``) in the
499  previous version of our script. That should explain the complaint.
500
501* It also behaves similar to "store_true" action.
502
503* Now here's a demonstration of what the "count" action gives. You've probably
504  seen this sort of usage before.
505
506* And if you don't specify the ``-v`` flag, that flag is considered to have
507  ``None`` value.
508
509* As should be expected, specifying the long form of the flag, we should get
510  the same output.
511
512* Sadly, our help output isn't very informative on the new ability our script
513  has acquired, but that can always be fixed by improving the documentation for
514  our script (e.g. via the ``help`` keyword argument).
515
516* That last output exposes a bug in our program.
517
518
519Let's fix::
520
521   import argparse
522   parser = argparse.ArgumentParser()
523   parser.add_argument("square", type=int,
524                       help="display a square of a given number")
525   parser.add_argument("-v", "--verbosity", action="count",
526                       help="increase output verbosity")
527   args = parser.parse_args()
528   answer = args.square**2
529
530   # bugfix: replace == with >=
531   if args.verbosity >= 2:
532       print("the square of {} equals {}".format(args.square, answer))
533   elif args.verbosity >= 1:
534       print("{}^2 == {}".format(args.square, answer))
535   else:
536       print(answer)
537
538And this is what it gives:
539
540.. code-block:: shell-session
541
542   $ python3 prog.py 4 -vvv
543   the square of 4 equals 16
544   $ python3 prog.py 4 -vvvv
545   the square of 4 equals 16
546   $ python3 prog.py 4
547   Traceback (most recent call last):
548     File "prog.py", line 11, in <module>
549       if args.verbosity >= 2:
550   TypeError: '>=' not supported between instances of 'NoneType' and 'int'
551
552
553* First output went well, and fixes the bug we had before.
554  That is, we want any value >= 2 to be as verbose as possible.
555
556* Third output not so good.
557
558Let's fix that bug::
559
560   import argparse
561   parser = argparse.ArgumentParser()
562   parser.add_argument("square", type=int,
563                       help="display a square of a given number")
564   parser.add_argument("-v", "--verbosity", action="count", default=0,
565                       help="increase output verbosity")
566   args = parser.parse_args()
567   answer = args.square**2
568   if args.verbosity >= 2:
569       print("the square of {} equals {}".format(args.square, answer))
570   elif args.verbosity >= 1:
571       print("{}^2 == {}".format(args.square, answer))
572   else:
573       print(answer)
574
575We've just introduced yet another keyword, ``default``.
576We've set it to ``0`` in order to make it comparable to the other int values.
577Remember that by default,
578if an optional argument isn't specified,
579it gets the ``None`` value, and that cannot be compared to an int value
580(hence the :exc:`TypeError` exception).
581
582And:
583
584.. code-block:: shell-session
585
586   $ python3 prog.py 4
587   16
588
589You can go quite far just with what we've learned so far,
590and we have only scratched the surface.
591The :mod:`argparse` module is very powerful,
592and we'll explore a bit more of it before we end this tutorial.
593
594
595Getting a little more advanced
596==============================
597
598What if we wanted to expand our tiny program to perform other powers,
599not just squares::
600
601   import argparse
602   parser = argparse.ArgumentParser()
603   parser.add_argument("x", type=int, help="the base")
604   parser.add_argument("y", type=int, help="the exponent")
605   parser.add_argument("-v", "--verbosity", action="count", default=0)
606   args = parser.parse_args()
607   answer = args.x**args.y
608   if args.verbosity >= 2:
609       print("{} to the power {} equals {}".format(args.x, args.y, answer))
610   elif args.verbosity >= 1:
611       print("{}^{} == {}".format(args.x, args.y, answer))
612   else:
613       print(answer)
614
615Output:
616
617.. code-block:: shell-session
618
619   $ python3 prog.py
620   usage: prog.py [-h] [-v] x y
621   prog.py: error: the following arguments are required: x, y
622   $ python3 prog.py -h
623   usage: prog.py [-h] [-v] x y
624
625   positional arguments:
626     x                the base
627     y                the exponent
628
629   optional arguments:
630     -h, --help       show this help message and exit
631     -v, --verbosity
632   $ python3 prog.py 4 2 -v
633   4^2 == 16
634
635
636Notice that so far we've been using verbosity level to *change* the text
637that gets displayed. The following example instead uses verbosity level
638to display *more* text instead::
639
640   import argparse
641   parser = argparse.ArgumentParser()
642   parser.add_argument("x", type=int, help="the base")
643   parser.add_argument("y", type=int, help="the exponent")
644   parser.add_argument("-v", "--verbosity", action="count", default=0)
645   args = parser.parse_args()
646   answer = args.x**args.y
647   if args.verbosity >= 2:
648       print("Running '{}'".format(__file__))
649   if args.verbosity >= 1:
650       print("{}^{} == ".format(args.x, args.y), end="")
651   print(answer)
652
653Output:
654
655.. code-block:: shell-session
656
657   $ python3 prog.py 4 2
658   16
659   $ python3 prog.py 4 2 -v
660   4^2 == 16
661   $ python3 prog.py 4 2 -vv
662   Running 'prog.py'
663   4^2 == 16
664
665
666Conflicting options
667-------------------
668
669So far, we have been working with two methods of an
670:class:`argparse.ArgumentParser` instance. Let's introduce a third one,
671:meth:`add_mutually_exclusive_group`. It allows for us to specify options that
672conflict with each other. Let's also change the rest of the program so that
673the new functionality makes more sense:
674we'll introduce the ``--quiet`` option,
675which will be the opposite of the ``--verbose`` one::
676
677   import argparse
678
679   parser = argparse.ArgumentParser()
680   group = parser.add_mutually_exclusive_group()
681   group.add_argument("-v", "--verbose", action="store_true")
682   group.add_argument("-q", "--quiet", action="store_true")
683   parser.add_argument("x", type=int, help="the base")
684   parser.add_argument("y", type=int, help="the exponent")
685   args = parser.parse_args()
686   answer = args.x**args.y
687
688   if args.quiet:
689       print(answer)
690   elif args.verbose:
691       print("{} to the power {} equals {}".format(args.x, args.y, answer))
692   else:
693       print("{}^{} == {}".format(args.x, args.y, answer))
694
695Our program is now simpler, and we've lost some functionality for the sake of
696demonstration. Anyways, here's the output:
697
698.. code-block:: shell-session
699
700   $ python3 prog.py 4 2
701   4^2 == 16
702   $ python3 prog.py 4 2 -q
703   16
704   $ python3 prog.py 4 2 -v
705   4 to the power 2 equals 16
706   $ python3 prog.py 4 2 -vq
707   usage: prog.py [-h] [-v | -q] x y
708   prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
709   $ python3 prog.py 4 2 -v --quiet
710   usage: prog.py [-h] [-v | -q] x y
711   prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
712
713That should be easy to follow. I've added that last output so you can see the
714sort of flexibility you get, i.e. mixing long form options with short form
715ones.
716
717Before we conclude, you probably want to tell your users the main purpose of
718your program, just in case they don't know::
719
720   import argparse
721
722   parser = argparse.ArgumentParser(description="calculate X to the power of Y")
723   group = parser.add_mutually_exclusive_group()
724   group.add_argument("-v", "--verbose", action="store_true")
725   group.add_argument("-q", "--quiet", action="store_true")
726   parser.add_argument("x", type=int, help="the base")
727   parser.add_argument("y", type=int, help="the exponent")
728   args = parser.parse_args()
729   answer = args.x**args.y
730
731   if args.quiet:
732       print(answer)
733   elif args.verbose:
734       print("{} to the power {} equals {}".format(args.x, args.y, answer))
735   else:
736       print("{}^{} == {}".format(args.x, args.y, answer))
737
738Note that slight difference in the usage text. Note the ``[-v | -q]``,
739which tells us that we can either use ``-v`` or ``-q``,
740but not both at the same time:
741
742.. code-block:: shell-session
743
744   $ python3 prog.py --help
745   usage: prog.py [-h] [-v | -q] x y
746
747   calculate X to the power of Y
748
749   positional arguments:
750     x              the base
751     y              the exponent
752
753   optional arguments:
754     -h, --help     show this help message and exit
755     -v, --verbose
756     -q, --quiet
757
758
759Conclusion
760==========
761
762The :mod:`argparse` module offers a lot more than shown here.
763Its docs are quite detailed and thorough, and full of examples.
764Having gone through this tutorial, you should easily digest them
765without feeling overwhelmed.
766