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