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