Recommended Video Course
Building Command Line Interfaces With argparse
Table of Contents
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding:Building Command Line Interfaces With argparse
When building Python command-line interfaces (CLI), Python’sargparse
module offers a comprehensive solution. You can useargparse
to create user-friendly command-line interfaces that parse arguments and options directly from the command line. This tutorial guides you through organizing CLI projects, adding arguments and options, and customizing your CLI’s behavior withargparse
.
You’ll also learn about setting up command-line argument parsers, parsing arguments, and implementing advanced features like subcommands and mutually exclusive argument groups. By understanding howargparse
handles errors and messages, you can create robust and intuitive CLIs for your Python applications.
By the end of this tutorial, you’ll understand that:
argparse
module.argparse
parses command-line arguments and generates help messages.argparse
by defining argument types and actions.To get the most out of this tutorial, you should be familiar with Python programming, including concepts such asobject-oriented programming,script development and execution, and Pythonpackages and modules. It’ll also be helpful if you’re familiar with general concepts and topics related to using a command line or terminal.
Get Your Code:Click here to download the free sample code that you’ll use to build command-line interfaces withargparse
.
Take the Quiz: Test your knowledge with our interactive “Build Command-Line Interfaces With Python's argparse” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Build Command-Line Interfaces With Python's argparseIn this quiz, you'll test your understanding of creating command-line interfaces (CLIs) in Python using the argparse module. This knowledge is essential for creating user-friendly command-line apps, which are common in development, data science, and systems administration.
Since the invention of computers, humans have always needed and found ways to interact and share information with these machines. The information exchange has flowed among humans,computer software, andhardware components. The shared boundary between any two of these elements is generically known as aninterface.
In software development, an interface is a special part of a given piece of software that allows interaction between components of a computer system. When it comes to human and software interaction, this vital component is known as theuser interface.
You’ll find different types of user interfaces in programming. Probably,graphical user interfaces (GUIs) are the most common today. However, you’ll also find apps and programs that providecommand-line interfaces (CLIs) for their users. In this tutorial, you’ll learn about CLIs and how to create them in Python.
Command-line interfaces allow you to interact with an application or program through your operating system command line,terminal, or console.
To understand command-line interfaces and how they work, consider this practical example. Say that you have a directory calledsample
containing three sample files. If you’re on aUnix-like operating system, such as Linux or macOS, go ahead and open a command-line window or terminal in the parent directory and then execute the following command:
$lssample/hello.txt lorem.md realpython.md
Thels
Unix command lists the files and subdirectories contained in a target directory, which defaults to the current working directory. The above command call doesn’t display much information about the content ofsample
. It only displays the filenames on the screen.
Note: If you’re on Windows, then you’ll have anls
command that works similarly to the Unixls
command. However, in its plain form, the command displays a different output:
PS>ls.\sample\ Directory: C:\sampleMode LastWriteTime Length Name---- ------------- ------ -----a--- 11/10/2022 10:06 AM 88 hello.txt-a--- 11/10/2022 10:06 AM 2629 lorem.md-a--- 11/10/2022 10:06 AM 429 realpython.md
The PowerShellls
command issues a table containing detailed information on every file and subdirectory under your target directory. So, the upcoming examples won’t work as expected on Windows systems.
Suppose you want richer information about your directory and its content. In that case, you don’t need to look around for a program other thanls
because this command has a full-featured command-line interface with a useful set ofoptions that you can use to customize the command’s behavior.
For example, go ahead and executels
with the-l
option:
$ls-lsample/total 24-rw-r--r--@ 1 user staff 83 Aug 17 22:15 hello.txt-rw-r--r--@ 1 user staff 2609 Aug 17 22:15 lorem.md-rw-r--r--@ 1 user staff 428 Aug 17 22:15 realpython.md
The output ofls
is quite different now. The command displays much more information about the files insample
, including permissions, owner, group, date, and size. It also shows the total space that these files use on your computer’s disk.
Note: To get a detailed list of all the options thatls
provides as part of its CLI, go ahead and run theman ls
command in your command line or terminal.
This richer output results from using the-l
option, which is part of the Unixls
command-line interface and enables the detailed output format.
Throughout this tutorial, you’ll learn aboutcommands andsubcommands. You’ll also learn about command-linearguments,options, andparameters, so you should incorporate these terms into your tech vocabulary:
Command: A program or routine that runs at the command line or terminal window. You’ll typically identify a command with the name of the underlying program or routine.
Argument: A required or optional piece of information that a command uses to perform its intended action. Commands typically accept one or many arguments, which you can provide as a whitespace-separated or comma-separated list on your command line.
Option, also known asflag orswitch: An optional argument that modifies a command’s behavior. Options are passed to commands using a specific name, like-l
in the previous example.
Parameter: An argument that an option uses to perform its intended operation or action.
Subcommand: A predefined name that can be passed to an application to run a specific action.
Consider the sample command construct from the previous section:
$ls-lsample/
In this example, you’ve combined the following components of a CLI:
ls
: The command’s name or the app’s name-l
: An option, switch, or flag that enables detailed outputssample
: An argument that provides additional information to the command’s executionNow consider the following command construct, which showcases the CLI of Python’s package manager, known aspip
:
$pipinstall-rrequirements.txt
This is a commonpip
command construct, which you’ve probably seen before. It allows you to install the requirements of a given Python project using arequirements.txt
file. In this example, you’re using the following CLI components:
pip
: The command’s nameinstall
: The name of a subcommand ofpip
-r
: An option of theinstall
subcommandrequirements.txt
: An argument, specifically a parameter of the-r
optionNow you know what command-line interfaces are and what their main parts or components are. It’s time to learn how to create your own CLIs in Python.
sys.argv
vsargparse
Python comes with a couple of tools that you can use to write command-line interfaces for your programs and apps. If you need to quickly create a minimal CLI for a small program, then you can use theargv
attribute from thesys
module. This attribute automatically stores the arguments that you pass to a given program at the command line.
sys.argv
to Build a Minimal CLIAs an example of usingargv
to create a minimal CLI, say that you need to write a small program that lists all the files in a given directory, similar to whatls
does. In this situation, you can write something like this:
ls_argv.py
importsysfrompathlibimportPathif(args_count:=len(sys.argv))>2:print(f"One argument expected, got{args_count-1}")raiseSystemExit(2)elifargs_count<2:print("You must specify the target directory")raiseSystemExit(2)target_dir=Path(sys.argv[1])ifnottarget_dir.is_dir():print("The target directory doesn't exist")raiseSystemExit(1)forentryintarget_dir.iterdir():print(entry.name)
This program implements a minimal CLI by manually processing the arguments provided at the command line, which are automatically stored insys.argv
. The first item insys.argv
is always the program’s name. The second item will be the target directory. The app shouldn’t accept more than one target directory, so theargs_count
must not exceed2
.
After checking the content ofsys.argv
, you create apathlib.Path
object to store the path to your target directory. If this directory doesn’t exist, then you inform the user and exit the app. Thefor
loop lists the directory content, one entry per line.
If yourun the script from your command line, then you’ll get the following results:
$pythonls_argv.pysample/hello.txtlorem.mdrealpython.md$pythonls_argv.pyYou must specify the target directory$pythonls_argv.pysample/other_dir/One argument expected, got 2$pythonls_argv.pynon_existing/The target directory doesn't exist
Your program takes a directory as an argument and lists its content. If you run the command without arguments, then you get an error message. If you run the command with more than one target directory, you also get an error. Running the command with a nonexistent directory produces another error message.
Even though your program works okay, parsing command-line arguments manually using thesys.argv
attribute isn’t a scalable solution for more complex CLI apps. If your app needs to take many more arguments and options, then parsingsys.argv
will be a complex and error-prone task. You need something better, and you get it in Python’sargparse
module.
argparse
A much more convenient way to create CLI apps in Python is using theargparse
module, which comes in thestandard library. This module was first released inPython 3.2 with PEP389 and is a quick way to create CLI apps in Python without installing a third-party library, such asTyper orClick.
This module was released as a replacement for the oldergetopt
andoptparse
modules because they lacked some important features.
Python’sargparse
module allows you to:
These features turnargparse
into a powerful CLI framework that you can confidently rely on when creating your CLI applications. To use Python’sargparse
, you’ll need to follow four straightforward steps:
argparse
.ArgumentParser
..add_argument()
method..parse_args()
on the parser to get theNamespace
of arguments.As an example, you can useargparse
to improve yourls_argv.py
script. Go ahead and createls.py
with the following code:
ls.py v1
importargparsefrompathlibimportPathparser=argparse.ArgumentParser()parser.add_argument("path")args=parser.parse_args()target_dir=Path(args.path)ifnottarget_dir.exists():print("The target directory doesn't exist")raiseSystemExit(1)forentryintarget_dir.iterdir():print(entry.name)
Your code has changed significantly with the introduction ofargparse
. The most notable difference from the previous version is that theconditional statements to check the arguments provided by the user are gone. That’s becauseargparse
automatically checks the presence of arguments for you.
In this new implementation, you first importargparse
and create an argument parser. To create the parser, you use theArgumentParser
class. Next, you define an argument calledpath
to get the user’s target directory.
The next step is to call.parse_args()
to parse the input arguments and get aNamespace
object that contains all the user’s arguments. Note that now theargs
variable holds aNamespace
object, which has a property for each argument that’s been gathered from the command line.
In this example, you only have one argument, calledpath
. TheNamespace
object allows you to accesspath
using thedot notation onargs
. The rest of your code remains the same as in the first implementation.
Now go ahead and run this new script from your command line:
$pythonls.pysample/lorem.mdrealpython.mdhello.txt$pythonls.pyusage: ls.py [-h] pathls.py: error: the following arguments are required: path$pythonls.pysample/other_dir/usage: ls.py [-h] pathls.py: error: unrecognized arguments: other_dir/$pythonls.pynon_existing/The target directory doesn't exist
The first commandprints the same output as your original script,ls_argv.py
. In contrast, the second command displays output that’s quite different from inls_argv.py
. The program now shows a usage message and issues an error telling you that you must provide thepath
argument.
In the third command, you pass two target directories, but the app isn’t prepared for that. Therefore, it shows the usage message again and throws an error letting you know about the underlying problem.
Finally, if you run the script with a nonexistent directory as an argument, then you get an error telling you that the target directory doesn’t exist, so the program can’t do its work.
A new implicit feature is now available to you. Now your program accepts an optional-h
flag. Go ahead and give it a try:
$pythonls.py-husage: ls.py [-h] pathpositional arguments: pathoptions: -h, --help show this help message and exit
Great, now your program automatically responds to the-h
or--help
flag, displaying a help message with usage instructions for you. That’s a really neat feature, and you get it for free by introducingargparse
into your code!
With this quick introduction to creating CLI apps in Python, you’re now ready to dive deeper into theargparse
module and all its cool features.
argparse
You can use theargparse
module to write user-friendly command-line interfaces for your applications and projects. This module allows you to define the arguments and options that your app will require. Thenargparse
will take care of parsing those arguments and options ofsys.argv
for you.
Another cool feature ofargparse
is that it automatically generates usage and help messages for your CLI apps. The module also issues errors in response to invalid arguments and more.
Before diving deeper intoargparse
, you need to know that the module’sdocumentation recognizes two different types of command-line arguments:
In thels.py
example,path
is apositional argument. Such an argument is calledpositional because its relative position in the command construct defines its purpose.
Optional arguments aren’t mandatory. They allow you to modify the behavior of the command. In thels
Unix command example, the-l
flag is an optional argument, which makes the command display a detailed output.
With these concepts clear, you can kick things off and start building your own CLI apps with Python andargparse
.
The command-line argument parser is the most important part of anyargparse
CLI. All the arguments and options that you provide at the command line will pass through this parser, which will do the hard work for you.
To create a command-line argument parser withargparse
, you need to instantiate theArgumentParser
class:
>>>fromargparseimportArgumentParser>>>parser=ArgumentParser()>>>parserArgumentParser( prog='', usage=None, description=None, formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)
Theconstructor ofArgumentParser
takes many different arguments that you can use to tweak several features of your CLIs. All of its arguments are optional, so the most bare-bones parser that you can create results from instantiatingArgumentParser
without any arguments.
You’ll learn more about the arguments to theArgumentParser
constructor throughout this tutorial, particularly in the section oncustomizing your argument parser. For now, you can tackle the next step in creating a CLI withargparse
. That step is to add arguments and options through the parser object.
To add arguments and options to anargparse
CLI, you’ll use the.add_argument()
method of yourArgumentParser
instance. Note that the method is common for arguments and options. Remember that in theargparse
terminology, arguments are calledpositional arguments, and options are known asoptional arguments.
The first argument to the.add_argument()
method sets the difference between arguments and options. This argument is identified as eithername
orflag
. So, if you provide aname
, then you’ll be defining an argument. In contrast, if you use aflag
, then you’ll add an option.
You’ve already worked with command-line arguments inargparse
. So, consider the following enhanced version of your customls
command, which adds an-l
option to the CLI:
ls.py v2
1importargparse 2importdatetime 3frompathlibimportPath 4 5parser=argparse.ArgumentParser() 6 7parser.add_argument("path") 8 9parser.add_argument("-l","--long",action="store_true")1011args=parser.parse_args()1213target_dir=Path(args.path)1415ifnottarget_dir.exists():16print("The target directory doesn't exist")17raiseSystemExit(1)1819defbuild_output(entry,long=False):20iflong:21size=entry.stat().st_size22date=datetime.datetime.fromtimestamp(23entry.stat().st_mtime).strftime(24"%b%d %H:%M:%S"25)26returnf"{size:>6d}{date}{entry.name}"27returnentry.name2829forentryintarget_dir.iterdir():30print(build_output(entry,long=args.long))
In this example, line 9 creates an option with the flags-l
and--long
. The syntactical difference between arguments and options is that option names start with-
for shorthand flags and--
for long flags.
Note that in this specific example, anaction
argument set to"store_true"
accompanies the-l
or--long
option, which means that this option will store aBoolean value. If you provide the option at the command line, then its value will beTrue
. If you miss the option, then its value will beFalse
. You’ll learn more about theaction
argument to.add_argument()
in theSetting the Action Behind an Option section.
Thebuild_output()
function on line 19returns a detailed output whenlong
isTrue
and a minimal output otherwise. The detailed output will contain the size, modification date, and name of all the entries in the target directory. It uses tools like thePath.stat()
and adatetime.datetime
object with a customstring format.
Go ahead and execute your program onsample
to check how the-l
option works:
$pythonls.py-lsample/ 2609 Oct 28 14:07:04 lorem.md 428 Oct 28 14:07:04 realpython.md 83 Oct 28 14:07:04 hello.txt
Your new-l
option allows you to generate and display a more detailed output about the content of your target directory.
Now that you know how to add command-line arguments and options to your CLIs, it’s time to dive into parsing those arguments and options. That’s what you’ll explore in the following section.
Parsing the command-line arguments is another important step in any CLI app based onargparse
. Once you’ve parsed the arguments, then you can start taking action in response to their values. In your customls
command example, the argument parsing happens on the line containing theargs = parser.parse_args()
statement.
This statement calls the.parse_args()
method and assigns its return value to theargs
variable. The return value of.parse_args()
is aNamespace
object containing all the arguments and options provided at the command line and their corresponding values.
Consider the following toy example:
>>>fromargparseimportArgumentParser>>>parser=ArgumentParser()>>>parser.add_argument("site")_StoreAction(...)>>>parser.add_argument("-c","--connect",action="store_true")_StoreTrueAction(...)>>>args=parser.parse_args(["Real Python","-c"])>>>argsNamespace(site='Real Python', connect=True)>>>args.site'Real Python'>>>args.connectTrue
TheNamespace
object that results from calling.parse_args()
on the command-line argument parser gives you access to all the input arguments, options, and their corresponding values by using thedot notation. This way, you can check the list of input arguments and options to take actions in response to the user’s choices at the command line.
You’ll use thisNamespace
object in your application’s main code. That’s what you did under thefor
loop in your customls
command example.
Up to this point, you’ve learned about the main steps for creatingargparse
CLIs. Now you can take some time to learn the basics of how to organize and build a CLI application in Python.
Before continuing with yourargparse
learning adventure, you should pause and think of how you would organize your code andlay out a CLI project. First, you should observe the following points:
__main__.py
module to any Python package if you want to make that package directly executable.With these ideas in mind and considering that themodel-view-controller (MVC) pattern is an effective way to structure your applications, you can use the following directory structure when laying out a CLI project:
hello_cli/│├── hello_cli/│ ├── __init__.py│ ├── __main__.py│ ├── cli.py│ └── model.py│├── tests/│ ├── __init__.py│ ├── test_cli.py│ └── test_model.py│├── pyproject.toml├── README.md└── requirements.txt
Thehello_cli/
directory is the project’s root directory. There, you’ll place the following files:
pyproject.toml
is aTOML file that specifies the project’sbuild system and otherconfigurations.README.md
provides the projectdescription andinstructions for installing and running the application. Adding a descriptive and detailedREADME.md
file to your projects is a best practice in programming, especially if you’re planning to release the project as an open-source solution.requirements.txt
provides a conventional file that lists the project’sexternal dependencies. You’ll use this file to automatically install the dependencies usingpip
with the-r
option.Then you have thehello_cli/
directory that holds the app’s core package, which contains the following modules:
__init__.py
enableshello_cli/
as a Pythonpackage.__main__.py
provides the application’sentry-point script or executable file.cli.py
provides the application’s command-line interface. The code in this file will play theview-controller role in the MVC-based architecture.model.py
contains the code that supports the app’s main functionalities. This code will play themodel role in your MVC layout.You’ll also have atests/
package containing files withunit tests for your app’s components. In this specific project layout example, you havetest_cli.py
for unit tests that check the CLI’s functionality andtest_model.py
for unit tests that check your model’s code.
Thepyproject.toml
file allows you to define the app’s build system as well as many other general configurations. Here’s a minimal example of how to fill in this file for your samplehello_cli
project:
# pyproject.toml[build-system]requires=["setuptools>=64.0.0","wheel"]build-backend="setuptools.build_meta"[project]name="hello_cli"version="0.0.1"description="My awesome Hello CLI application"readme="README.md"authors=[{name="Real Python",email="info@realpython.com"}][project.scripts]hello_cli="hello_cli.__main__:main"
The[build-system]
table header sets upsetuptools
as your app’sbuild system and specifies which dependencies Python needs to install for building your app. The[project]
header provides general metadata for your application. This metadata is pretty useful when you want topublish your app to the Python package index (PyPI). Finally, the[project.scripts]
heading defines the entry point to your application.
With this quick dive into laying out and building CLI projects, you’re ready to continue learning aboutargparse
, especially how to customize your command-line argument parser.
In previous sections, you learned the basics of using Python’sargparse
to implement command-line interfaces for your programs or applications. You also learned how to organize and lay out a CLI app project following the MVC pattern.
In the following sections, you’ll dive deeper into many other neat features ofargparse
. Specifically, you’ll learn how to use some of the most useful arguments in theArgumentParser
constructor, which will allow you to customize the general behavior of your CLI apps.
Providing usage instructions and help to the users of your CLI applications is a best practice that’ll make your users’ lives more pleasant with a greatuser experience (UX). In this section, you’ll learn how to take advantage of some arguments ofArgumentParser
to fine-tune how your CLI apps show help and usage messages to their users. You’ll learn how to:
To kick things off, you’ll start by setting your program’s name and specifying how that name will look in the context of a help or usage message.
By default,argparse
uses the first value insys.argv
to set the program’s name. This first item holds the name of the Python file that you’ve just executed. This filename will look odd in a usage message.
As an example, go ahead and run your customls
command with the-h
option:
$pythonls.py-husage: ls.py [-h] [-l] pathpositional arguments: pathoptions: -h, --help show this help message and exit -l, --long
The highlighted line in the command’s output shows thatargparse
is using the filenamels.py
as the program’s name. This looks odd because app names rarely include file extensions when displayed in usage messages.
Fortunately, you can specify the name of your program by using theprog
argument like in the following code snippet:
ls.py v3
importargparseimportdatetimefrompathlibimportPathparser=argparse.ArgumentParser(prog="ls")# ...forentryintarget_dir.iterdir():print(build_output(entry,long=args.long))
With theprog
argument, you specify the program name that’ll be used in the usage message. In this example, you use the"ls"
string. Now go ahead and run your app again:
$pythonls.py-husage: ls [-h] [-l] pathpositional arguments: pathoptions: -h, --help show this help message and exit -l, --long
Great! The app’s usage message in the first line of this output showsls
instead ofls.py
as the program’s name.
Apart from setting the program’s name,argparse
lets you define the app’s description and epilog message. You’ll learn how to do both in the following section.
You can also define a general description for your application and an epilog or closing message. To do this, you’ll use thedescription
andepilog
arguments, respectively. Go ahead and update thels.py
file with the following additions to theArgumentParser
constructor:
ls.py v4
importargparseimportdatetimefrompathlibimportPathparser=argparse.ArgumentParser(prog="ls",description="List the content of a directory",epilog="Thanks for using%(prog)s! :)",)# ...forentryintarget_dir.iterdir():print(build_output(entry,long=args.long))
In this update,description
allows you to provide a general description for your app. This description will display at the beginning of the help message. Theepilog
argument lets you define some text as your app’s epilog or closing message. Note that you can interpolate theprog
argument into the epilog string using theold-style string-formatting operator (%
).
Note: Help messages supportformat specifiers of the form%(specifier)s
. These specifiers use the string formatting operator,%
, rather than the popularf-strings. That’s because f-strings replace names with their values immediately as they run.
Therefore, insertingprog
intoepilog
in the call toArgumentParser
above will fail with aNameError
if you use an f-string.
If you run the app again, then you’ll get an output like the following:
$pythonls.py-husage: ls [-h] [-l] pathList the content of a directorypositional arguments: pathoptions: -h, --help show this help message and exit -l, --longThanks for using ls! :)
Now the output shows the description message right after the usage message and the epilog message at the end of the help text.
Help groups are another interesting feature ofargparse
. They allow you to group related commands and arguments, which will help you organize the app’s help message. To create these help groups, you’ll use the.add_argument_group()
method ofArgumentParser
.
As an example, consider the following updated version of your customls
command:
ls.py v5
# ...parser=argparse.ArgumentParser(prog="ls",description="List the content of a directory",epilog="Thanks for using%(prog)s! :)",)general=parser.add_argument_group("general output")general.add_argument("path")detailed=parser.add_argument_group("detailed output")detailed.add_argument("-l","--long",action="store_true")args=parser.parse_args()# ...forentryintarget_dir.iterdir():print(build_output(entry,long=args.long))
In this update, you create a help group for arguments and options that display general output and another group for arguments and options that display detailed output.
Note: In this specific example, grouping arguments like this may seem unnecessary. However, if your app has several arguments and options, then using help groups can significantly improve your user experience.
If you run the app with the-h
option at your command line, then you’ll get the following output:
$pythonls.py-husage: ls [-h] [-l] pathList the content of a directoryoptions: -h, --help show this help message and exitgeneral output: pathdetailed output: -l, --longThanks for using ls! :)
Now your app’s arguments and options are conveniently grouped under descriptive headings in the help message. This neat feature will help you provide more context to your users and improve their understanding of how the app works.
Beyond customizing the usage and help messages,ArgumentParser
also allows you to perform a few other interesting tweaks to your CLI apps. Some of these tweaks include:
Sometimes, you may need to specify a singleglobal default value for your app’s arguments and options. You can do this by passing the default value toargument_default
on the call to theArgumentParser
constructor.
This feature may be only rarely useful because arguments and options often have a different data type or meaning, and it can be difficult to find a value that suits all the requirements.
However, a common use case ofargument_default
is when you want to avoid adding arguments and options to theNamespace
object. In this situation, you can use theSUPPRESS
constant as the default value. This default value will make it so that only the arguments and options provided at the command line end up stored in the argumentsNamespace
.
As an example, go ahead and modify your customls
command as in the snippet below:
ls.py v6
importargparseimportdatetimefrompathlibimportPathparser=argparse.ArgumentParser(prog="ls",description="List the content of a directory",epilog="Thanks for using%(prog)s! :)",argument_default=argparse.SUPPRESS,)# ...forentryintarget_dir.iterdir():try:long=args.longexceptAttributeError:long=Falseprint(build_output(entry,long=long))
By passingSUPPRESS
to theArgumentParser
constructor, you prevent non-supplied arguments from being stored in the argumentsNamespace
object. That’s why you have to check if the-l
or--long
option was actually passed before callingbuild_output()
. Otherwise, your code will break with anAttributeError
becauselong
won’t be present inargs
.
Another cool feature ofArgumentParser
is that it allows you toload argument values from an external file. This possibility comes in handy when you have an application with long or complicated command-line constructs, and you want to automate the process of loading argument values.
In this situation, you can store the argument values in an external file and ask your program to load them from it. To try this feature out, go ahead and create the following toy CLI app:
fromfile.py
importargparseparser=argparse.ArgumentParser(fromfile_prefix_chars="@")parser.add_argument("one")parser.add_argument("two")parser.add_argument("three")args=parser.parse_args()print(args)
Here, you pass the@
symbol to thefromfile_prefix_chars
argument ofArgumentParser
. Then you create three required arguments that must be provided at the command line.
Now say that you often use this application with the same set of argument values. To facilitate and streamline your work, you can create a file containing appropriate values for all the necessary arguments, one per line, like in the followingargs.txt
file:
firstsecondthird
With this file in place, you can now call your program and instruct it to load the values from theargs.txt
file like in the following command run:
$pythonfromfile.py@args.txtNamespace(one='first', two='second', three='third')
In this command’s output, you can see thatargparse
has read the content ofargs.txt
and sequentially assigned values to each argument of yourfromfile.py
program. All the arguments and their values are successfully stored in theNamespace
object.
The ability to acceptabbreviated option names is another cool feature ofargparse
CLIs. This feature is enabled by default and comes in handy when your program has long option names. As an example, consider the following program, which prints out the value that you specify at the command line under the--argument-with-a-long-name
option:
abbreviate.py
importargparseparser=argparse.ArgumentParser()parser.add_argument("--argument-with-a-long-name")args=parser.parse_args()print(args.argument_with_a_long_name)
This program prints whatever your pass as an argument to the--argument-with-a-long-name
option. Go ahead and run the following commands to check how the Pythonargparse
module handles abbreviations for you:
$pythonabbreviate.py--argument-with-a-long-name4242$pythonabbreviate.py--argument4242$pythonabbreviate.py--a4242
These examples show how you can abbreviate the name of the--argument-with-a-long-name
option and still get the app to work correctly. This feature is enabled by default. If you want to disable it and forbid abbreviations, then you can use theallow_abbrev
argument toArgumentParser
:
abbreviate.py
importargparseparser=argparse.ArgumentParser(allow_abbrev=False)parser.add_argument("--argument-with-a-long-name")args=parser.parse_args()print(args.argument_with_a_long_name)
Settingallow_abbrev
toFalse
disables abbreviations in command-line options. From this point on, you’ll have to provide the complete option name for the program to work correctly. Otherwise, you’ll get an error:
$pythonabbreviate.py--argument-with-a-long-name4242$pythonabbreviate.py--argument42usage: abbreviate.py [-h] [--argument-with-a-long-name ...]abbreviate.py: error: unrecognized arguments: --argument 42
The error message in the second example tells you that the--argument
option isn’t recognized as a valid option. To use the option, you need to provide its full name.
Up to this point, you’ve learned how to customize several features of theArgumentParser
class to improve the user experience of your CLIs. Now you know how to tweak the usage and help messages of your apps and how to fine-tune some global aspects of command-line arguments and options.
In this section, you’ll learn how to customize several other features of your CLI’s command-line arguments and options. In this case, you’ll be using the.add_argument()
method and some of its most relevant arguments, includingaction
,type
,nargs
,default
,help
, and a few others.
When you add an option or flag to a command-line interface, you’ll often need to define how you want to store the option’s value in theNamespace
object that results from calling.parse_args()
. To do this, you’ll use theaction
argument to.add_argument()
. Theaction
argument defaults to"store"
, which means that the value provided for the option at hand will be stored as is in theNamespace
.
Theaction
argument can take one of several possible values. Here’s the list of these possible values and their meanings:
Allowed Value | Description |
---|---|
store | Stores the input value to theNamespace object |
store_const | Stores a constant value when the option is specified |
store_true | Stores theTrue Boolean value when the option is specified and storesFalse otherwise |
store_false | StoresFalse when the option is specified and storesTrue otherwise |
append | Appends the current value to alist each time the option is provided |
append_const | Appends a constant value to a list each time the option is provided |
count | Stores the number of times the current option has been provided |
version | Shows the app’s version and terminates the execution |
In this table, the values that include the_const
suffix in their names require you to provide the desired constant value using theconst
argument in the call to the.add_argument()
method. Similarly, theversion
action requires you to provide the app’s version by passing theversion
argument to.add_argument()
. You should also note that only thestore
andappend
actions can and must take arguments at the command line.
To try these actions out, you can create a toy app with the following implementation:
actions.py
importargparseparser=argparse.ArgumentParser()parser.add_argument("--name",action="store")# Equivalent to parser.add_argument("--name")parser.add_argument("--pi",action="store_const",const=3.14)parser.add_argument("--is-valid",action="store_true")parser.add_argument("--is-invalid",action="store_false")parser.add_argument("--item",action="append")parser.add_argument("--repeated",action="append_const",const=42)parser.add_argument("--add-one",action="count")parser.add_argument("--version",action="version",version="%(prog)s 0.1.0")args=parser.parse_args()print(args)
This program implements an option for each type ofaction
discussed above. Then the program prints the resultingNamespace
of arguments. Here’s a summary of how these options will work:
--name
will store the value passed, without any further consideration.
--pi
will automatically store the target constant when the option is provided.
--is-valid
will storeTrue
when provided andFalse
otherwise. If you need the opposite behavior, use astore_false
action like--is-invalid
in this example.
--item
will let you create a list of all the values. You must repeat the option for each value. Under the hood,argparse
will append the items to a list named after the option itself.
--repeated
will work similarly to--item
. However, it always appends the same constant value, which you must provide using theconst
argument.
--add-one
counts how many times the option is passed at the command line. This type of option is quite useful when you want to implement several verbosity levels in your programs. For example,-v
can mean level one of verbosity,-vv
may indicate level two, and so on.
--version
shows the app’s version and terminates the execution immediately. Note that you must provide the version number beforehand, which you can do by using theversion
argument when creating the option with.add_argument()
.
Go ahead and run the script with the following command construct to try out all these options:
PS>pythonactions.py`>--namePython`>--pi`>--is-valid`>--is-invalid`>--item1--item2--item3`>--repeat--repeat--repeat`>--add-one--add-one--add-oneNamespace( name='Python', pi=3.14, is_valid=True, is_invalid=False, item=['1', '2', '3'], repeated=[42, 42, 42], add_one=3)PS>pythonactions.py--versionactions.py 0.1.0
$pythonactions.py\--namePython\--pi\--is-valid\--is-invalid\--item1--item2--item3\--repeat--repeat--repeat\--add-one--add-one--add-oneNamespace( name='Python', pi=3.14, is_valid=True, is_invalid=False, item=['1', '2', '3'], repeated=[42, 42, 42], add_one=3)$pythonactions.py--versionactions.py 0.1.0
With this command, you show how all the actions work and how they’re stored in the resultingNamespace
object. Theversion
action is the last one that you used, because this option just shows the version of the program and then ends the execution. It doesn’t get stored in theNamespace
object.
Even though the default set of actions is quite complete, you also have the possibility of creating custom actions by subclassing theargparse.Action
class. If you decide to do this, then you must override the.__call__()
method, which turnsinstances into callable objects. Optionally, you can override the.__init__()
and.format_usage()
methods depending on your needs.
To override the.__call__()
method, you need to ensure that the method’s signature includes theparser
,namespace
,values
, andoption_string
arguments.
In the following example, you implement a minimal and verbosestore
action that you can use when building your CLI apps:
custom_action.py
importargparseclassVerboseStore(argparse.Action):def__call__(self,parser,namespace,values,option_string=None):print(f"Storing{values} in the{option_string} option...")setattr(namespace,self.dest,values)parser=argparse.ArgumentParser()parser.add_argument("-n","--name",action=VerboseStore)args=parser.parse_args()print(args)
In this example, you defineVerboseStore
inheriting fromargparse.Action
. Then you override the.__call__()
method to print an informative message and set the target option in the namespace of command-line arguments. Finally, the app prints the namespace itself.
Go ahead and run the following command to try out your custom action:
$pythoncustom_action.py--namePythonStoring Python in the --name option...Namespace(name='Python')
Great! Your program now prints out a message before storing the value provided to the--name
option at the command line. Custom actions like the one in the above example allow you to fine-tune how your programs’ options are stored.
To continue fine-tuning yourargparse
CLIs, you’ll learn how to customize the input value of command-line arguments and options in the following section.
Another common requirement when you’re building CLI applications is to customize the input values that arguments and options will accept at the command line. For example, you may require that a given argument accept an integer value, a list of values, a string, and so on.
By default, any argument provided at the command line will be treated as a string. Fortunately,argparse
has internal mechanisms to check if a given argument is a valid integer, string, list, and more.
In this section, you’ll learn how to customize the way in whichargparse
processes and stores input values. Specifically, you’ll learn how to:
To kick things off, you’ll start by customizing the data type that your arguments and options will accept at the command line.
When creatingargparse
CLIs, you can define the type that you want to use when storing command-line arguments and options in theNamespace
object. To do this, you can use thetype
argument of.add_argument()
.
As an example, say that you want to write a sample CLI app for dividing twonumbers. The app will take two options,--dividend
and--divisor
. These options will only accept integer numbers at the command line:
divide.py
importargparseparser=argparse.ArgumentParser()parser.add_argument("--dividend",type=int)parser.add_argument("--divisor",type=int)args=parser.parse_args()print(args.dividend/args.divisor)
In this example, you set the type of--dividend
and--divisor
toint
. This setting will make your options only accept valid integer values as input. If the input value can’t be converted to theint
type without losing information, then you’ll get an error:
$pythondivide.py--dividend42--divisor221.0$pythondivide.py--dividend"42"--divisor"2"21.0$pythondivide.py--dividend42--divisor2.0usage: divide.py [-h] [--dividend DIVIDEND] [--divisor DIVISOR]divide.py: error: argument --divisor: invalid int value: '2.0'$pythondivide.py--dividend42--divisortwousage: divide.py [-h] [--dividend DIVIDEND] [--divisor DIVISOR]divide.py: error: argument --divisor: invalid int value: 'two'
The first two examples work correctly because the input values are integer numbers. The third example fails with an error because the divisor is a floating-point number. The last example also fails becausetwo
isn’t a numeric value.
Taking multiple values in arguments and options may be a requirement in some of your CLI applications. By default,argparse
assumes that you’ll expect a single value for each argument or option. You can modify this behavior with thenargs
argument of.add_argument()
.
Thenargs
argument tellsargparse
that the underlying argument can take zero or more input values depending on the specific value assigned tonargs
. If you want the argument or option to accept a fixed number of input values, then you can setnargs
to an integer number. If you need more flexible behaviors, thennargs
has you covered because it also accepts the following values:
Allowed Value | Meaning |
---|---|
? | Accepts a single input value, which can be optional |
* | Takes zero or more input values, which will be stored in a list |
+ | Takes one or more input values, which will be stored in a list |
argparse.REMAINDER | Gathers all the values that are remaining in the command line |
It’s important to note that this list of allowed values fornargs
works for both command-line arguments and options.
To start trying out the allowed values fornargs
, go ahead and create apoint.py
file with the following code:
point.py
importargparseparser=argparse.ArgumentParser()parser.add_argument("--coordinates",nargs=2)args=parser.parse_args()print(args)
In this small app, you create a command-line option called--coordinates
that takes two input values representing thex
andy
Cartesian coordinates. With this script in place, go ahead and run the following commands:
$pythonpoint.py--coordinates23Namespace(coordinates=['2', '3'])$pythonpoint.py--coordinates2usage: point.py [-h] [--coordinates COORDINATES COORDINATES]point.py: error: argument --coordinates: expected 2 arguments$pythonpoint.py--coordinates234usage: point.py [-h] [--coordinates COORDINATES COORDINATES]point.py: error: unrecognized arguments: 4$pythonpoint.py--coordinatesusage: point.py [-h] [--coordinates COORDINATES COORDINATES]point.py: error: argument --coordinates: expected 2 arguments
In the first command, you pass two numbers as input values to--coordinates
. In this case, the program works correctly, storing the values in a list under thecoordinates
attribute in theNamespace
object.
In the second example, you pass a single input value, and the program fails. The error message tells you that the app was expecting two arguments, but you only provided one. The third example is pretty similar, but in that case, you supplied more input values than required.
The final example also fails because you didn’t provide input values at all, and the--coordinates
option requires two values. In this example, the two input values are mandatory.
To try out the*
value ofnargs
, say that you need a CLI app that takes a list of numbers at the command line and returns their sum:
sum.py
importargparseparser=argparse.ArgumentParser()parser.add_argument("numbers",nargs="*",type=float)args=parser.parse_args()print(sum(args.numbers))
Thenumbers
argument accepts zero or more floating-point numbers at the command line because you’ve setnargs
to*
. Here’s how this script works:
$pythonsum.py1236.0$pythonsum.py12345621.0$pythonsum.py0
The first two commands show thatnumbers
accepts an undetermined number of values at the command line. These values will be stored in a list named after the argument itself in theNamespace
object. If you don’t pass any values tosum.py
, then the corresponding list of values will be empty, and the sum will be0
.
Next up, you can try the+
value ofnargs
with another small example. This time, say that you need an app that accepts one or more files at the command line. You can code this app like in the example below:
files.py
importargparseparser=argparse.ArgumentParser()parser.add_argument("files",nargs="+")args=parser.parse_args()print(args)
Thefiles
argument in this example will accept one or more values at the command line. You can give it a try by running the following commands:
$pythonfiles.pyhello.txtNamespace(files=['hello.txt'])$pythonfiles.pyhello.txtrealpython.mdREADME.mdNamespace(files=['hello.txt', 'realpython.md', 'README.md'])$pythonfiles.pyusage: files.py [-h] files [files ...]files.py: error: the following arguments are required: files
The first two examples show thatfiles
accepts an undefined number of files at the command line. The last example shows that you can’t usefiles
without providing a file, as you’ll get an error. This behavior forces you to provide at least one file to thefiles
argument.
The final allowed value fornargs
isREMAINDER
. This constant allows you to capture the remaining values provided at the command line. If you pass this value tonargs
, then the underlying argument will work as a bag that’ll gather all the extra input values. As an exercise, go ahead and explore howREMAINDER
works by coding a small app by yourself.
Even though thenargs
argument gives you a lot of flexibility, sometimes it’s pretty challenging to use this argument correctly in multiple command-line options and arguments. For example, it can be hard to reliably combine arguments and options withnargs
set to*
,+
, orREMAINDER
in the same CLI:
cooking.py
importargparseparser=argparse.ArgumentParser()parser.add_argument("veggies",nargs="+")parser.add_argument("fruits",nargs="*")args=parser.parse_args()print(args)
In this example, theveggies
argument will accept one or more vegetables, while thefruits
argument should accept zero or more fruits at the command line. Unfortunately, this example doesn’t work as expected:
$pythoncooking.pypeppertomatoapplebananaNamespace(veggies=['pepper', 'tomato', 'apple', 'banana'], fruits=[])
The command’s output shows that all the provided input values have been stored in theveggies
attribute, while thefruits
attribute holds an empty list. This happens because theargparse
parser doesn’t have a reliable way to determine which value goes to which argument or option. In this specific example, you can fix the problem by turning both arguments into options:
cooking.py
importargparseparser=argparse.ArgumentParser()parser.add_argument("--veggies",nargs="+")parser.add_argument("--fruits",nargs="*")args=parser.parse_args()print(args)
With this minor update, you’re ensuring that the parser will have a secure way to parse the values provided at the command line. Go ahead and run the following command to confirm this:
$pythoncooking.py--veggiespeppertomato--fruitsapplebananaNamespace(veggies=['pepper', 'tomato'], fruits=['apple', 'banana'])
Now each input value has been stored in the correct list in the resultingNamespace
. Theargparse
parser has used the option names to correctly parse each supplied value.
To avoid issues similar to the one discussed in the above example, you should always be careful when trying to combine arguments and options withnargs
set to*
,+
, orREMAINDER
.
The.add_argument()
method can take adefault
argument that allows you to provide an appropriate default value for individual arguments and options. This feature can be useful when you need the target argument or option to always have a valid value in case the user doesn’t provide any input at the command line.
As an example, get back to your customls
command and say that you need to make the command list the content of the current directory when the user doesn’t provide a target directory. You can do this by settingdefault
to"."
like in the code below:
ls.py v7
importargparseimportdatetimefrompathlibimportPath# ...general=parser.add_argument_group("general output")general.add_argument("path",nargs="?",default=".")# ...
The highlighted line in this code snippet does the magic. In the call to.add_argument()
, you usenargs
with the question mark (?
) as its value. You need to do this because all the command-line arguments inargparse
are required, and settingnargs
to either?
,*
, or+
is the only way to skip the required input value. In this specific example, you use?
because you need a single input value or none.
Then you setdefault
to the"."
string, which represents the current working directory. With these updates, you can now runls.py
without providing a target directory. It’ll list the content of its default directory. To try it out, go ahead and run the following commands:
$cdsample/$python../ls.pylorem.mdrealpython.mdhello.txt
Now your customls
command lists the current directory’s content if you don’t provide a target directory at the command line. Isn’t that cool?
Another interesting possibility inargparse
CLIs is that you can create a domain of allowed values for a specific argument or option. You can do this by providing a list of accepted values using thechoices
argument of.add_argument()
.
Here’s an example of a small app with a--size
option that only accepts a few predefined input values:
size.py
importargparseparser=argparse.ArgumentParser()parser.add_argument("--size",choices=["S","M","L","XL"],default="M")args=parser.parse_args()print(args)
In this example, you use thechoices
argument to provide a list of allowed values for the--size
option. This setting will cause the option to only accept the predefined values. If you try to use a value that’s not in the list, then you get an error:
$pythonsize.py--sizeSNamespace(size='S')$pythonchoices.py--sizeAusage: choices.py [-h] [--size {S,M,L,XL}]choices.py: error: argument --size: invalid choice: 'A' (choose from 'S', 'M', 'L', 'XL')
If you use an input value from the list of allowed values, then your app works correctly. If you use an extraneous value, then the app fails with an error.
Thechoices
argument can hold a list of allowed values, which can be of different data types. For integer values, a useful technique is to use a range of accepted values. To do this, you can userange()
like in the following example:
weekdays.py
importargparsemy_parser=argparse.ArgumentParser()my_parser.add_argument("--weekday",type=int,choices=range(1,8))args=my_parser.parse_args()print(args)
In this example, the value provided at the command line will be automatically checked against therange
object provided as thechoices
argument. Go ahead and give this example a try by running the following commands:
$pythondays.py--weekday2Namespace(weekday=2)$pythondays.py--weekday6Namespace(weekday=6)$pythondays.py--weekday9usage: days.py [-h] [--weekday {1,2,3,4,5,6,7}]days.py: error: argument --weekday: invalid choice: 9 (choose from 1, 2, 3, 4, 5, 6, 7)
The first two examples work correctly because the input number is in the allowed range of values. However, if the input number is outside the defined range, like in the last example, then your app fails, displaying usage and error messages.
As you already know, a great feature ofargparse
is that it generates automatic usage and help messages for your applications. You can access these messages using the-h
or--help
flag, which is included by default in anyargparse
CLI.
Up to this point, you’ve learned how to provide description and epilog messages for your apps. In this section, you’ll continue improving your app’s help and usage messages by providing enhanced messages for individual command-line arguments and options. To do this, you’ll use thehelp
andmetavar
arguments of.add_argument()
.
Go back to your customls
command and run the script with the-h
switch to check its current output:
$pythonls.py-husage: ls [-h] [-l] [path]List the content of a directoryoptions: -h, --help show this help message and exitgeneral output: pathdetailed output: -l, --longThanks for using ls! :)
This output looks nice, and it’s a good example of howargparse
saves you a lot of work by providing usage and help message out of the box.
Note that only the-h
or--help
option shows a descriptive help message. In contrast, your own argumentspath
and-l
or--long
don’t show a help message. To fix that, you can use thehelp
argument.
Open yourls.py
and update it like in the following code:
ls.py v8
importargparseimportdatetimefrompathlibimportPath# ...general=parser.add_argument_group("general output")general.add_argument("path",nargs="?",default=".",help="take the path to the target directory (default:%(default)s)",)detailed=parser.add_argument_group("detailed output")detailed.add_argument("-l","--long",action="store_true",help="display detailed directory content",)# ...
In this update tols.py
, you use thehelp
argument of.add_argument()
to provide specific help messages for your arguments and options.
Note: As you already know, help messages support format specifiers like%(prog)s
. You can use most of the arguments toadd_argument()
as format specifiers. For example,%(default)s
,%(type)s
, and so on.
Now go ahead and run the app with the-h
flag again:
$pythonls.py-husage: ls [-h] [-l] [path]List the content of a directoryoptions: -h, --help show this help message and exitgeneral output: path take the path to the target directory (default: .)detailed output: -l, --long display detailed directory contentThanks for using ls! :)
Now bothpath
and-l
show descriptive help messages when you run the app with the-h
flag. Note thatpath
includes its default value in its help message, which provides valuable information to your users.
Another desired feature is to have a nice and readable usage message in your CLI apps. The default usage message ofargparse
is pretty good already. However, you can use themetavar
argument of.add_argument()
to slightly improve it.
Themetavar
argument comes in handy when a command-line argument or option accepts input values. It allows you to give this input value a descriptive name that the parser can use to generate the help message.
As an example of when to usemetavar
, go back to yourpoint.py
example:
point.py
importargparseparser=argparse.ArgumentParser()parser.add_argument("--coordinates",nargs=2)args=parser.parse_args()print(args)
If you run this application from your command line with the-h
switch, then you get an output that’ll look like the following:
$pythonpoint.py-husage: point.py [-h] [--coordinates COORDINATES COORDINATES]options: -h, --help show this help message and exit --coordinates COORDINATES COORDINATES
By default,argparse
uses the original name of command-line options to designate their corresponding input values in the usage and help messages, as you can see in the highlighted lines. In this specific example, the nameCOORDINATES
in the plural may be confusing. Should your users provide the point’s coordinates two times?
You can remove this ambiguity by using themetavar
argument:
point.py
importargparseparser=argparse.ArgumentParser()parser.add_argument("--coordinates",nargs=2,metavar=("X","Y"),help="take the Cartesian coordinates%(metavar)s",)args=parser.parse_args()print(args)
In this example, you use a tuple as the value tometavar
. The tuple contains the two coordinate names that people commonly use to designate a pair of Cartesian coordinates. You also provide a custom help message for--coordinates,
including a format specifier with themetavar
argument.
If you run the script with the-h
flag, then you get the following output:
$pythoncoordinates.py-husage: coordinates.py [-h] [--coordinates X Y]options: -h, --help show this help message and exit --coordinates X Y take the Cartesian coordinates ('X', 'Y')
Now your app’s usage and help messages are way clearer than before. Now your users will immediately know that they need to provide two numeric values,X
andY
, for the--coordinates
option to work correctly.
Another interesting feature that you can incorporate into yourargparse
CLIs is the ability to create mutually exclusive groups of arguments and options. This feature comes in handy when you have arguments or options that can’t coexist in the same command construct.
Consider the following CLI app, which has--verbose
and--silent
options that can’t coexist in the same command call:
groups.py
importargparseparser=argparse.ArgumentParser()group=parser.add_mutually_exclusive_group(required=True)group.add_argument("-v","--verbose",action="store_true")group.add_argument("-s","--silent",action="store_true")args=parser.parse_args()print(args)
Having mutually exclusive groups for--verbose
and--silent
makes it impossible to use both options in the same command call:
$pythongroups.py-v-susage: groups.py [-h] (-v | -s)groups.py: error: argument -s/--silent: not allowed with argument -v/--verbose
You can’t specify the-v
and-s
flags in the same command call. If you try to do it, then you get an error telling you that both options aren’t allowed at the same time.
Note that the app’s usage message showcases that-v
and-s
are mutually exclusive by using the pipe symbol (|
) to separate them. This way of presenting the options must be interpreted asuse-v
or-s
, but not both.
Some command-line applications take advantage of subcommands to provide new features and functionalities. Applications likepip
,pyenv,Poetry, andgit
, which are pretty popular among Python developers, make extensive use of subcommands.
For example, if you runpip
with the--help
switch, then you’ll get the app’s usage and help message, which includes the complete list of subcommands:
$pip--helpUsage: pip <command> [options]Commands: install Install packages. download Download packages. uninstall Uninstall packages. ...
To use one of these subcommands, you just need to list it after the app’s name. For example, the following command will list all the packages you’ve installed in your current Python environment:
$piplistPackage Version---------- -------pip x.y.zsetuptools x.y.z ...
Providing subcommands in your CLI applications is quite a useful feature. Fortunately,argparse
also provides the required tool to implement this feature. If you want to arm your command-line apps with subcommands, then you can use the.add_subparsers()
method ofArgumentParser
.
As an example of using.add_subparsers()
, say you want to create a CLI app to perform basic arithmetic operations, including addition, subtraction, multiplication, and division. You want to implement these operations as subcommands in your app’s CLI.
To build this app, you start by coding the app’s core functionality, or the arithmetic operations themselves. Then you add the corresponding arguments to the app’s CLI:
calc.py
1importargparse 2 3defadd(a,b): 4returna+b 5 6defsub(a,b): 7returna-b 8 9defmul(a,b):10returna*b1112defdiv(a,b):13returna/b1415global_parser=argparse.ArgumentParser(prog="calc")16subparsers=global_parser.add_subparsers(17title="subcommands",help="arithmetic operations"18)1920arg_template={21"dest":"operands",22"type":float,23"nargs":2,24"metavar":"OPERAND",25"help":"a numeric value",26}2728add_parser=subparsers.add_parser("add",help="add two numbers a and b")29add_parser.add_argument(**arg_template)30add_parser.set_defaults(func=add)3132sub_parser=subparsers.add_parser("sub",help="subtract two numbers a and b")33sub_parser.add_argument(**arg_template)34sub_parser.set_defaults(func=sub)3536mul_parser=subparsers.add_parser("mul",help="multiply two numbers a and b")37mul_parser.add_argument(**arg_template)38mul_parser.set_defaults(func=mul)3940div_parser=subparsers.add_parser("div",help="divide two numbers a and b")41div_parser.add_argument(**arg_template)42div_parser.set_defaults(func=div)4344args=global_parser.parse_args()4546print(args.func(*args.operands))
Here’s a breakdown of how the code works:
Lines 3 to 13 define four functions that perform the basic arithmetic operations of addition, subtraction, multiplication, and division. These functions will provide the operations behind each of your app’s subcommands.
Line 15 defines the command-line argument parser as usual.
Lines 16 to 18 define a subparser by calling.add_subparsers()
. In this call, you provide a title and a help message.
Lines 20 to 26 define a template for your command-line arguments. This template is a dictionary containing sensitive values for the required arguments of.add_argument()
. Each argument will be calledoperands
and will consist of two floating-point values. Defining this template allows you to avoid repetitive code when creating the command-line arguments.
Line 28 adds a parser to the subparser object. The name of this subparser isadd
and will represent your subcommand for addition operations. Thehelp
argument defines a help message for this parser in particular.
Line 29 adds theoperands
command-line argument to theadd
subparser using.add_argument()
with the argument template. Note that you need to use thedictionary unpacking operator (**
) to extract the argument template fromarg_template
.
Line 30 uses.set_defaults()
to assign theadd()
callback function to theadd
subparser or subcommand.
Lines 32 to 42 perform actions similar to those in lines 28 to 30 for the rest of your three subcommands,sub
,mul
, anddiv
. Finally, line 46 calls thefunc
attribute fromargs
. This attribute will automatically call the function associated with the subcommand at hand.
Go ahead and try out your new CLI calculator by running the following commands:
$pythoncalc.pyadd3811.0$pythoncalc.pysub15510.0$pythoncalc.pymul21242.0$pythoncalc.pydiv1226.0$pythoncalc.py-husage: calc [-h] {add,sub,mul,div} ...options: -h, --help show this help message and exitsubcommands: {add,sub,mul,div} arithmetic operations add add two numbers a and b sub subtract two numbers a and b mul multiply two numbers a and b div divide two numbers a and b$pythoncalc.pydiv-husage: calc div [-h] OPERAND OPERANDpositional arguments: OPERAND a numeric valueoptions: -h, --help show this help message and exit
Cool! All your subcommands work as expected. They take two numbers and perform the target arithmetic operation with them. Note that now you have usage and help messages for the app and for each subcommand too.
When creating CLI applications, you’ll find situations in which you’ll need to terminate the execution of an app because of an error or an exception. A common practice in this situation is to exit the app while emitting anerror code orexit status so that other apps or the operating system can understand that the app has terminated because of an error in its execution.
Typically, if a command exits with a zero code, then it has succeeded. Meanwhile, a nonzero exit status indicates a failure. The drawback of this system is that while you have a single, well-defined way to indicate success, you have various ways to indicate failure, depending on the problem at hand.
Unfortunately, there’s no definitive standard for error codes or exit statuses. Operating systems and programming languages use different styles, including decimal orhexadecimal numbers, alphanumeric codes, and even a phrase describing the error. Unix programs generally use2
for command-line syntax errors and1
for all other errors.
In Python, you’ll commonly use integer values to specify the system exit status of a CLI app. If your code returnsNone
, then the exit status is zero, which is considered asuccessful termination. Any nonzero value meansabnormal termination. Most systems require the exit code to be in the range from0
to127
, and produce undefined results otherwise.
When building CLI apps withargparse
, you don’t need to worry about returning exit codes for successful operations. However, you should return an appropriate exit code when your app abruptly terminates its execution due to an error other than command syntax errors, in which caseargparse
does the work for you out of the box.
Theargparse
module, specifically theArgumentParser
class, has two dedicated methods for terminating an app when something isn’t going well:
Method | Description |
---|---|
.exit(status=0, message=None) | Terminates the app, returning the specifiedstatus and printingmessage if given |
.error(message) | Prints a usage message that incorporates the providedmessage and terminates the app with a status code of2 |
Both methods print directly to thestandard error stream, which is dedicated to error reporting. The.exit()
method is appropriate when you need complete control over which status code to return. On the other hand, the.error()
method is internally used byargparse
for command-line syntax errors, but you can use it whenever it’s necessary and appropriate.
As an example of when to use these methods, consider the following update to your customls
command:
ls.py v9
importargparseimportdatetimefrompathlibimportPath# ...target_dir=Path(args.path)ifnottarget_dir.exists():parser.exit(1,message="The target directory doesn't exist")# ...
In the conditional statement that checks if the target directory exists, instead of usingraise SystemExit(1)
, you useArgumentParser.exit()
. This makes your code more focused on the selected tech stack, which is theargparse
framework.
To check how your app behaves now, go ahead and run the following commands:
PS>pythonls.py.\non_existing\Thetargetdirectorydoesn'texistPS>echo$LASTEXITCODE1
$pythonls.pynon_existing/The target directory doesn't exist$echo$?1
The app terminates its execution immediately when the target directory doesn’t exist. If you’re on a Unix-like system, such as Linux or macOS, then you can inspect the$?
shell variable to confirm that your app has returned1
to signal an error in its execution. If you’re on Windows, then you can check the contents of the$LASTEXITCODE
variable.
Providing consistent status codes in your CLI applications is a best practice that’ll allow you and your users to successfully integrate your app in their shell scripting and command pipes.
Now you know what a command-line interface is and what its main components are, including arguments, options, and subcommands. You also learned how to create fully functionalCLI applications using theargparse
module from the Python standard library.
In this tutorial, you’ve learned how to:
argparse
to createcommand-line interfacesargparse
Knowing how to write effective and intuitive command-line interfaces is a great skill to have as a developer. Writing good CLIs for your apps allows you to give your users a pleasant user experience while interacting with your applications.
Get Your Code:Click here to download the free sample code that you’ll use to build command-line interfaces withargparse
.
Now that you have some experience building command-line interfaces with Python’sargparse
, you can use the questions and answers below to check your understanding and recap what you’ve learned.
These FAQs are related to the most important concepts you’ve covered in this tutorial. Click theShow/Hide toggle beside each question to reveal the answer.
You use theargparse
module to create user-friendly command-line interfaces, allowing you to define the arguments your app requires and automatically generate help and usage messages.
You add arguments inargparse
by calling the.add_argument()
method on anArgumentParser
instance, specifying the argument name and any options or flags it should have.
You handle optional arguments inargparse
by using flags or switches that start with a dash (-
) or double dash (--
). You can specify their behavior using theaction
parameter in.add_argument()
.
You can set a default value for an argument inargparse
by using thedefault
parameter in the.add_argument()
method, specifying the value the argument should take if the user doesn’t provide it.
sys.argv
provides a simple way to access command-line arguments as a list of strings, whileargparse
offers a more powerful and flexible approach for parsing, validating, and handling command-line arguments.
Take the Quiz: Test your knowledge with our interactive “Build Command-Line Interfaces With Python's argparse” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Build Command-Line Interfaces With Python's argparseIn this quiz, you'll test your understanding of creating command-line interfaces (CLIs) in Python using the argparse module. This knowledge is essential for creating user-friendly command-line apps, which are common in development, data science, and systems administration.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding:Building Command Line Interfaces With argparse
🐍 Python Tricks 💌
Get a short & sweetPython Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.
AboutLeodanis Pozo Ramos
Leodanis is a self-taught Python developer, educator, and technical writer with over 10 years of experience.
» More about LeodanisMasterReal-World Python Skills With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
MasterReal-World Python Skills
With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
What Do You Think?
What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.
Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students.Get tips for asking good questions andget answers to common questions in our support portal.
Keep Learning
Related Topics:intermediatepython
Recommended Video Course:Building Command Line Interfaces With argparse
Related Tutorials:
Already have an account?Sign-In
Almost there! Complete this form and click the button below to gain instant access:
Build Command-Line Interfaces With Python's argparse (Source Code)