• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   options:
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   options:
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   options:
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   options:
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   options:
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   options:
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(f"the square of {args.square} equals {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(f"the square of {args.square} equals {answer}")
391   elif args.verbosity == 1:
392       print(f"{args.square}^2 == {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(f"the square of {args.square} equals {answer}")
425   elif args.verbosity == 1:
426       print(f"{args.square}^2 == {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   options:
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(f"the square of {args.square} equals {answer}")
465   elif args.verbosity == 1:
466       print(f"{args.square}^2 == {answer}")
467   else:
468       print(answer)
469
470We have introduced another action, "count",
471to count the number of occurrences of specific options.
472
473
474.. code-block:: shell-session
475
476   $ python3 prog.py 4
477   16
478   $ python3 prog.py 4 -v
479   4^2 == 16
480   $ python3 prog.py 4 -vv
481   the square of 4 equals 16
482   $ python3 prog.py 4 --verbosity --verbosity
483   the square of 4 equals 16
484   $ python3 prog.py 4 -v 1
485   usage: prog.py [-h] [-v] square
486   prog.py: error: unrecognized arguments: 1
487   $ python3 prog.py 4 -h
488   usage: prog.py [-h] [-v] square
489
490   positional arguments:
491     square           display a square of a given number
492
493   options:
494     -h, --help       show this help message and exit
495     -v, --verbosity  increase output verbosity
496   $ python3 prog.py 4 -vvv
497   16
498
499* Yes, it's now more of a flag (similar to ``action="store_true"``) in the
500  previous version of our script. That should explain the complaint.
501
502* It also behaves similar to "store_true" action.
503
504* Now here's a demonstration of what the "count" action gives. You've probably
505  seen this sort of usage before.
506
507* And if you don't specify the ``-v`` flag, that flag is considered to have
508  ``None`` value.
509
510* As should be expected, specifying the long form of the flag, we should get
511  the same output.
512
513* Sadly, our help output isn't very informative on the new ability our script
514  has acquired, but that can always be fixed by improving the documentation for
515  our script (e.g. via the ``help`` keyword argument).
516
517* That last output exposes a bug in our program.
518
519
520Let's fix::
521
522   import argparse
523   parser = argparse.ArgumentParser()
524   parser.add_argument("square", type=int,
525                       help="display a square of a given number")
526   parser.add_argument("-v", "--verbosity", action="count",
527                       help="increase output verbosity")
528   args = parser.parse_args()
529   answer = args.square**2
530
531   # bugfix: replace == with >=
532   if args.verbosity >= 2:
533       print(f"the square of {args.square} equals {answer}")
534   elif args.verbosity >= 1:
535       print(f"{args.square}^2 == {answer}")
536   else:
537       print(answer)
538
539And this is what it gives:
540
541.. code-block:: shell-session
542
543   $ python3 prog.py 4 -vvv
544   the square of 4 equals 16
545   $ python3 prog.py 4 -vvvv
546   the square of 4 equals 16
547   $ python3 prog.py 4
548   Traceback (most recent call last):
549     File "prog.py", line 11, in <module>
550       if args.verbosity >= 2:
551   TypeError: '>=' not supported between instances of 'NoneType' and 'int'
552
553
554* First output went well, and fixes the bug we had before.
555  That is, we want any value >= 2 to be as verbose as possible.
556
557* Third output not so good.
558
559Let's fix that bug::
560
561   import argparse
562   parser = argparse.ArgumentParser()
563   parser.add_argument("square", type=int,
564                       help="display a square of a given number")
565   parser.add_argument("-v", "--verbosity", action="count", default=0,
566                       help="increase output verbosity")
567   args = parser.parse_args()
568   answer = args.square**2
569   if args.verbosity >= 2:
570       print(f"the square of {args.square} equals {answer}")
571   elif args.verbosity >= 1:
572       print(f"{args.square}^2 == {answer}")
573   else:
574       print(answer)
575
576We've just introduced yet another keyword, ``default``.
577We've set it to ``0`` in order to make it comparable to the other int values.
578Remember that by default,
579if an optional argument isn't specified,
580it gets the ``None`` value, and that cannot be compared to an int value
581(hence the :exc:`TypeError` exception).
582
583And:
584
585.. code-block:: shell-session
586
587   $ python3 prog.py 4
588   16
589
590You can go quite far just with what we've learned so far,
591and we have only scratched the surface.
592The :mod:`argparse` module is very powerful,
593and we'll explore a bit more of it before we end this tutorial.
594
595
596Getting a little more advanced
597==============================
598
599What if we wanted to expand our tiny program to perform other powers,
600not just squares::
601
602   import argparse
603   parser = argparse.ArgumentParser()
604   parser.add_argument("x", type=int, help="the base")
605   parser.add_argument("y", type=int, help="the exponent")
606   parser.add_argument("-v", "--verbosity", action="count", default=0)
607   args = parser.parse_args()
608   answer = args.x**args.y
609   if args.verbosity >= 2:
610       print(f"{args.x} to the power {args.y} equals {answer}")
611   elif args.verbosity >= 1:
612       print(f"{args.x}^{args.y} == {answer}")
613   else:
614       print(answer)
615
616Output:
617
618.. code-block:: shell-session
619
620   $ python3 prog.py
621   usage: prog.py [-h] [-v] x y
622   prog.py: error: the following arguments are required: x, y
623   $ python3 prog.py -h
624   usage: prog.py [-h] [-v] x y
625
626   positional arguments:
627     x                the base
628     y                the exponent
629
630   options:
631     -h, --help       show this help message and exit
632     -v, --verbosity
633   $ python3 prog.py 4 2 -v
634   4^2 == 16
635
636
637Notice that so far we've been using verbosity level to *change* the text
638that gets displayed. The following example instead uses verbosity level
639to display *more* text instead::
640
641   import argparse
642   parser = argparse.ArgumentParser()
643   parser.add_argument("x", type=int, help="the base")
644   parser.add_argument("y", type=int, help="the exponent")
645   parser.add_argument("-v", "--verbosity", action="count", default=0)
646   args = parser.parse_args()
647   answer = args.x**args.y
648   if args.verbosity >= 2:
649       print(f"Running '{__file__}'")
650   if args.verbosity >= 1:
651       print(f"{args.x}^{args.y} == ", end="")
652   print(answer)
653
654Output:
655
656.. code-block:: shell-session
657
658   $ python3 prog.py 4 2
659   16
660   $ python3 prog.py 4 2 -v
661   4^2 == 16
662   $ python3 prog.py 4 2 -vv
663   Running 'prog.py'
664   4^2 == 16
665
666
667Conflicting options
668-------------------
669
670So far, we have been working with two methods of an
671:class:`argparse.ArgumentParser` instance. Let's introduce a third one,
672:meth:`add_mutually_exclusive_group`. It allows for us to specify options that
673conflict with each other. Let's also change the rest of the program so that
674the new functionality makes more sense:
675we'll introduce the ``--quiet`` option,
676which will be the opposite of the ``--verbose`` one::
677
678   import argparse
679
680   parser = argparse.ArgumentParser()
681   group = parser.add_mutually_exclusive_group()
682   group.add_argument("-v", "--verbose", action="store_true")
683   group.add_argument("-q", "--quiet", action="store_true")
684   parser.add_argument("x", type=int, help="the base")
685   parser.add_argument("y", type=int, help="the exponent")
686   args = parser.parse_args()
687   answer = args.x**args.y
688
689   if args.quiet:
690       print(answer)
691   elif args.verbose:
692       print(f"{args.x} to the power {args.y} equals {answer}")
693   else:
694       print(f"{args.x}^{args.y} == {answer}")
695
696Our program is now simpler, and we've lost some functionality for the sake of
697demonstration. Anyways, here's the output:
698
699.. code-block:: shell-session
700
701   $ python3 prog.py 4 2
702   4^2 == 16
703   $ python3 prog.py 4 2 -q
704   16
705   $ python3 prog.py 4 2 -v
706   4 to the power 2 equals 16
707   $ python3 prog.py 4 2 -vq
708   usage: prog.py [-h] [-v | -q] x y
709   prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
710   $ python3 prog.py 4 2 -v --quiet
711   usage: prog.py [-h] [-v | -q] x y
712   prog.py: error: argument -q/--quiet: not allowed with argument -v/--verbose
713
714That should be easy to follow. I've added that last output so you can see the
715sort of flexibility you get, i.e. mixing long form options with short form
716ones.
717
718Before we conclude, you probably want to tell your users the main purpose of
719your program, just in case they don't know::
720
721   import argparse
722
723   parser = argparse.ArgumentParser(description="calculate X to the power of Y")
724   group = parser.add_mutually_exclusive_group()
725   group.add_argument("-v", "--verbose", action="store_true")
726   group.add_argument("-q", "--quiet", action="store_true")
727   parser.add_argument("x", type=int, help="the base")
728   parser.add_argument("y", type=int, help="the exponent")
729   args = parser.parse_args()
730   answer = args.x**args.y
731
732   if args.quiet:
733       print(answer)
734   elif args.verbose:
735       print("{} to the power {} equals {}".format(args.x, args.y, answer))
736   else:
737       print("{}^{} == {}".format(args.x, args.y, answer))
738
739Note that slight difference in the usage text. Note the ``[-v | -q]``,
740which tells us that we can either use ``-v`` or ``-q``,
741but not both at the same time:
742
743.. code-block:: shell-session
744
745   $ python3 prog.py --help
746   usage: prog.py [-h] [-v | -q] x y
747
748   calculate X to the power of Y
749
750   positional arguments:
751     x              the base
752     y              the exponent
753
754   options:
755     -h, --help     show this help message and exit
756     -v, --verbose
757     -q, --quiet
758
759
760Conclusion
761==========
762
763The :mod:`argparse` module offers a lot more than shown here.
764Its docs are quite detailed and thorough, and full of examples.
765Having gone through this tutorial, you should easily digest them
766without feeling overwhelmed.
767