Skip to content

Instantly share code, notes, and snippets.

@anne17
Last active October 30, 2023 12:45
Show Gist options
  • Save anne17/3c7997bab881bc5f976ad7cb91adeba7 to your computer and use it in GitHub Desktop.
Save anne17/3c7997bab881bc5f976ad7cb91adeba7 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# A Quick Introduction to Argparse\n",
"\n",
"The following tutorial is a quick and rather superficial tutorial on how to add some command line arguments to a python script with the [argparse library](https://docs.python.org/3/library/argparse.html).\n",
"\n",
"## A simple script\n",
"Let's say we have this very simple script called `my_script.py` with three different functions:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def main_function():\n",
" print(\"\\nExecuting this cool main function!\\n\")\n",
"\n",
"\n",
"def other_function():\n",
" print(\"\\nExecuting this OTHER great function!\\n\")\n",
"\n",
"\n",
"def lame_function():\n",
" print(\"\\nExecuting this really lame function!\\n\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's say each time we run our script, we only want to run one of these functions. To to this, we can place some boilerplate code at the end of our script that let's us choose which functions to run, depending on which one is uncommented."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"if __name__ == \"__main__\":\n",
" main_function()\n",
" # other_function()\n",
" # lame_function()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Executing the script from the command line will give us the following output:\n",
"```\n",
"> python my_script.py\n",
"\n",
"Executing this cool main function!\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If we want to run `other_function` instead of `main_function`, we'd have to open the script, comment out `main_function` and uncomment `other_function`. This can get pretty tedious! It would be much nicer to to able to run our code from the command line and supply an argument in order to specify which function we want to run. This can be done with the built-in [argparse library](https://docs.python.org/3/library/argparse.html)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Adding a command line parser with subparsers\n",
"Let's import argparse and instantiate a parser that can take care of our command line arguments:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import argparse\n",
"\n",
"parser = argparse.ArgumentParser()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we'll tell the parser that it will have mandatory subparsers. Here we'll call them \"commands\" and we want each command to correspond to one of our functions. Then we'll define three different subparsers, one for each command (`main`, `other` and `lame`) and write a small help text for each of them."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"subparsers = parser.add_subparsers(dest=\"command\", title=\"commands\", metavar=\"<command>\")\n",
"subparsers.required = True\n",
"\n",
"main_parser = subparsers.add_parser(\"main\", help=\"Run main function\")\n",
"other_parser = subparsers.add_parser(\"other\", help=\"Run the other function\")\n",
"lame_parser = subparsers.add_parser(\"lame\", help=\"Run lame function\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We'll have to change our boilerplate code a little. Instead of calling one of our functions we'll collect the command line input (saved in the `args.command` variable because we set `dest=\"command\"` in our subparsers above), check for the different commands defined in our subparsers and execute the corresponding function."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"if __name__ == \"__main__\":\n",
" args = parser.parse_args()\n",
"\n",
" if args.command == \"main\":\n",
" main_function()\n",
" if args.command == \"other\":\n",
" other_function()\n",
" if args.command == \"lame\":\n",
" lame_function()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If we were to run our script now without providing any command we'd get an error message:\n",
"```\n",
"> python my_script.py\n",
"usage: my_script.py [-h] <command> ...\n",
"my_script.py: error: the following arguments are required: <command>\n",
"```\n",
"\n",
"This happens because we set `subparsers.required = True`, stating that the use of the subparsers is mandatory. This means that the script won't run if we don't supply any command.\n",
"\n",
"One of the perks with argparse is that it automatically creates a help function for us! So if we don't know what commands there are we can always ask the program by supplying the `-h` (or `--help`) flag:\n",
"\n",
"```\n",
"> python my_script.py -h\n",
"usage: my_script.py [-h] <command> ...\n",
"\n",
"options:\n",
" -h, --help show this help message and exit\n",
"\n",
"commands:\n",
" <command>\n",
" main Run main function\n",
" other Run the other function\n",
" lame Run lame function\n",
"```\n",
"\n",
"So now let's try one of our commands:\n",
"```\n",
"> python my_scriptar.py main\n",
"\n",
"Executing this cool main function!\n",
"\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Adding a flag\n",
"Great! But what if we want to add some flags to our commands? Let's say that `main_function` has a verbose flag which, if supplied, prints some more info:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def main_function(verbose=True):\n",
" print(\"\\nExecuting this cool main function!\\n\")\n",
"\n",
" if verbose:\n",
" print(\"And here comes more text output because we're in verbose mode!\\n\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's make this flag available via the command line by adding it to the subparser which handles our `main` command. By specifying `action=\"store_true\"` we're telling it that this flag is a boolean which is set to `True` if the flag is being used."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"main_parser = subparsers.add_parser(\"main\", help=\"Run main function\")\n",
"main_parser.add_argument(\"--verbose\", \"-v\", action=\"store_true\", help=\"Turn on verbose mode\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now all we have to do is collect the verbose flag from `args` and feed it into `main_function`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"if __name__ == \"__main__\":\n",
" args = parser.parse_args()\n",
"\n",
" if args.command == \"main\":\n",
" main_function(verbose=args.verbose)\n",
" if args.command == \"other\":\n",
" other_function()\n",
" if args.command == \"lame\":\n",
" lame_function()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When calling main with the `-h` flag we'll see that is now has a verbose flag\n",
"\n",
"```\n",
"> python my_script.py main -h\n",
"usage: my_script.py main [-h] [--verbose]\n",
"\n",
"options:\n",
" -h, --help show this help message and exit\n",
" --verbose, -v Turn on verbose mode\n",
"```\n",
"\n",
"And now `main_function` prints some extra output when we call it with the `-v` (or `--verbose`) flag:\n",
"\n",
"```\n",
"> python my_script.py main -v\n",
"\n",
"Executing this cool main function!\n",
"\n",
"And here comes more text output because we're in verbose mode!\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Keyword arguments and validation\n",
"Now let's have a look at one last neat argparse feature: argument validation. Instead of having a flag which is used to turn some functionality on of off, we can of course also have a keyword argument with some individual user input by ommitting `action=\"store_true\"` when defining the argument. When adding a keyword argument to a parser it is possible to provide the data type the argument should have by specifying the `type` argument. The type can for example be `int`, `float` or a callable function. Argparse will then try to convert the user input into the correct data type before passing it on.\n",
"\n",
"In the following example we'll modify `other_function` so that it takes an argument `date` and prints it in the format 'MM/DD/YYYY'. We want the user to provide the date as a keyword argument in the format `YYYY-MM-DD`. The function `valid_date` will do the validation for us and raise an Exception in case the user supplies anything that cannot be parsed as a date in the format `YYYY-MM-DD`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import datetime\n",
"\n",
"\n",
"def other_function(date):\n",
" print(\"\\nExecuting this OTHER great function!\\n\")\n",
" newformat = date.strftime(\"%m/%d/%Y\")\n",
" print(newformat)\n",
"\n",
"\n",
"def valid_date(s):\n",
" \"\"\"Make sure that s is a valid date in the correct format.\"\"\"\n",
" try:\n",
" return datetime.datetime.strptime(s, \"%Y-%m-%d\")\n",
" except ValueError:\n",
" msg = f\"Not a valid date of format YYYY-MM-DD: {s}\"\n",
" raise argparse.ArgumentTypeError(msg)\n",
"\n",
"\n",
"other_parser.add_argument(\"-d\", \"--date\", type=valid_date, default=None, help=\"reformat a given date as 'MM/DD/YYYY'\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And of course we need to adapt our boilerplate code in order to feed the `date` argument into `other_function`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"if __name__ == \"__main__\":\n",
" args = parser.parse_args()\n",
"\n",
" if args.command == \"main\":\n",
" main_function(verbose=args.verbose)\n",
" if args.command == \"other\":\n",
" other_function(date=args.date)\n",
" if args.command == \"lame\":\n",
" lame_function()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's check the help function of the `other` command:\n",
"```\n",
"> python my_script.py other -h\n",
"usage: my_script.py other [-h] [-d DATE]\n",
"\n",
"options:\n",
" -h, --help show this help message and exit\n",
" -d DATE, --date DATE reformat a given date as 'mm/dd/yyyy'\n",
"```\n",
"\n",
"Now let's try to supply something that is not a date:\n",
"```\n",
"> python my_script.py other -d hello?\n",
"usage: my_script.py other [-h] [-d DATE]\n",
"my_script.py other: error: argument -d/--date: Not a valid date of format YYYY-MM-DD: hello?\n",
"```\n",
"\n",
"And finally we'll supply a date in the required format as keyword argument to the `other` command:\n",
"```\n",
"> python my_script.py other -d 2023-10-30\n",
"\n",
"Executing this OTHER great function!\n",
"\n",
"10/30/2023\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## More argparse\n",
"Congratulations, now you know some argparse basics! 🎉 Of course the library has much more to offer. For more details you can always checkout the official [argparse documentation](https://docs.python.org/3/library/argparse.html) or read [this tutorial](https://docs.python.org/3/howto/argparse.html) which goes a little deeper."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment