Sharpen Your Coding Skills with Command-Line Arguments in Python

dylan hudson
6 min readApr 6, 2022

--

Python is a fantastic language for scripting, prototyping, and developing portable back-end applications. The extensive libraries and multi-platform support allow you to modify, modularize, and iterate quickly. One handy technique that can save you headaches and make your systems more secure is using command-line arguments (CLAs). Often we only consider using these after the application reaches a certain level of complexity or requires a UI for release, but implementing them early can actually help you write better code and close up security holes. Let’s look at a few reasons why, and then demo some code (scroll on down to section 4 if you just want to see the ‘how’).

1. Better Design from the Beginning

If we’re just building some quick automation, or prototyping an in-house application, it’s often tempting to just hard-code parameters and resist abstraction to save time in the short-term. But time invested in design and modularity often pays huge dividends, whether you find yourself needing to refactor frequently to incorporate changes, or the time is lost to debugging as the program grows. A solid foundation always saves time.

Command line arguments are a great way to force yourself to consider what parameters you might want to specify at runtime/deployment, which is also a great starting point for making your code modular, i.e. reusable and flexible (not to mention easier to debug). Using well-named variables for these will increase the code’s readability, and allow you to iterate more quickly with less errors. Plus, even if you don’t need them at first, it’s often much more annoying and time-consuming to go back later and modify the code.

One common example is processing files. Python is great for data analysis applications like logfile parsing, text filtering, scientific simulations, etc: if you’re going to download anything or use a local file, just go ahead and make that filename or URL a command-line argument. It speeds up early testing, and the associated infrastructure makes it both more portable and easily extensible (if you later need to process multiple files or a directory for example).

2. More Flexibility = Less Time Wasted

A huge benefit to command-line arguments is the efficient flexibility they allow. It’s not always easy to anticipate your own future use-cases, but you’ll develop that intuition with experience…and adding some CLA infrastructure in early will nearly always help.
A commonly-seen example is the use of boolean “flag” arguments to turn features on (or off), e.g. verbose output or error logging. Using ‘option’ arguments lets you specify a string for input during invocation (which you can interpret as an integer in Python if you like). Familiar uses here include specifying a file or folder name for reading or writing, or how many lines of a file to display.

Sometimes CLA’s can even be worth it just for debugging purposes as well- maybe you don’t intend to change the URL for API calls after deployment, but you’d like to test several different ones in a dev environment first. Allowing your program to read this in could save you from re-deploying the code each time just to make the URL changes- and it could help you automate the testing, as you could wrap test code around the script, and programmatically invoke it with each URL as an argument (e.g. using a library like subprocess).

3. Best Practices for Security

If your script or app uses credentials (e.g. username/password, an API key, etc) to interact with other systems, or maybe has private internal URIs, it’s best not to hardcode any of these. It’s too easy to forget and upload them to a public Github repository or have an unaware colleague deploy the code to an unsecured server, not knowing there’s sensitive info in the source itself.
Setting these options at runtime is generally more efficient anyway, as credentials can change, API keys can expire, or custom endpoints can be updated, all of which would force a full redeployment of the code instead of just restarting the program and passing in the new information.

If you have multiple environment variables (i.e. other than credentials) that’d you like to set in this way, sometimes using a ‘config’ file is a better option. You can load the necessary info into a simple text file, and then pass the name (and path) of that file into your script with just one argument. Just don’t upload this config file to your github, or leave it unsecured!

4. Okay, I’m Convinced: Let’s See Some Code

The simplest way to do this is pretty straightforward: with just the sys module, we can get direct access to an array that contains all the arguments provided. Just like in C code, the first argument is the name of the program itself, and the subsequent elements in the list are any arguments that were provided. So, it would look like this-
import sys
program_name = sys.argv[0]
first_argument = sys.argv[1]
second_argument = sys.argv[2]
third_argument = sys.argv[3]

…etc.
This is a good method for small scripts with maybe one or two arguments that you’ll just use personally, or maybe with close friends and family, but you’ll probably want some better features if it’s any more complex or you plan to release it for general use- as you can see, this method does not provide any features for labeling or naming arguments (e.g. —- option or -f ).

You could certainly use this array access to write your own handlers, but there’s another great library that can do this for us: argparse. It includes infrastructure to parse labeled arguments, display help messages if improper input is provided, and set default values if you wish to make user-provided arguments optional. The official documentation is pretty great, but we’ll provide an example or two here as quick-start reference.

Using Argparse

There are two main types of arguments we can work with when using argparse: optional and positional. Optional arguments are specified with a label preceded by two dashes, and followed by the user input,
e.g.--filename myfile.txt They can be placed in any order. Positional arguments do not require any labels, but must be in the expected order to be parsed correctly. For example, if your program takes input and output filenames as command-line arguments, you could set the first argument to be the input file, and the second the output file- python my_program.py input.txt output.txt . But if you switch the order of the file names, the output file would be parsed as the input file and vice-versa.

First, an example using optional arguments.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(‘--firstarg’, help=’explanatory help text here’)
parser.add_argument(‘--secondarg’, help=’explanatory help text here’)
args = parser.parse_args()

and to use the argument data, you can access it as member of the args object:
first_arg = args.firstarg
second_arg = args.secondard

So with the above syntax, we could invoke the program like this-
python my_program.py --firstarg hello --secondarg world
or like this:
python my_progam.py --secondarg world --firstarg hello
and the behavior would be same.

Now, if we wanted to use positional arguments, we would just omit the double dash when we call add_argument(), and the arguments will be parsed according to their order on invocation:
parser.add_argument(‘firstarg’, help=’explanatory help text here’)
parser.add_argument(‘secondarg’, help=’explanatory help text here’)
This would be invoked like so:
python my_program.py hello world
but if you reversed the order of the arguments, you would get “hello” as args.secondarg .

Two more useful features we’ll touch on briefly are specifying data types and setting defaults.
Setting a data type can be helpful in recognizing and handling incorrect input, and also make your code a little cleaner. If you know that a certain option should always be parsed as an int, you can force that:
parser.add_argument(‘--firstarg’, type=int, help=’explanatory help text here’)
You could also specify a string or float; see the full list of options here.

Setting defaults can be very handy as well, and is just as straightforward:
parser.add_argument(‘--firstarg’, default='hello', help=’explanatory help text here’)

Default values are helpful in simplifying the user experience if input is only necessary in certain modes or circumstances- rather than requiring a large list of arguments for every invocation, the user will only need to supply the minimum input necessary. They can also be helpful in avoiding or handling error — e.g. rather than throwing a Value Error and terminating if certain input was not supplied, you might choose to give the user a second chance to enter data, or proceed under default behavior.

Conclusion

Thanks for reading! Hopefully this was some helpful discussion on how command-line arguments can help you design and write better code, and a quick and clear reference for anyone just trying to get things running. Feel free to drop me a line in the comments if you have any questions, issues, or topics you’d like to see covered next!

--

--