Science and technology

When to decide on C or Python for a command-line interface

This article has a easy objective: to assist new Python builders with among the historical past and terminology round command-line interfaces (CLIs) and discover the right way to write these helpful applications in Python.

In the start…

First, a Unix perspective on command-line interface design.

Unix is a pc working system and the ancestor of Linux and macOS (and lots of different working programs as nicely). Before graphical consumer interfaces, the consumer interacted with the pc by way of a command-line immediate (consider right this moment’s Bash surroundings). The main language for creating these applications beneath Unix is C, which is amazingly powerful

So it behooves us to at the least perceive the fundamentals of a C program.

Assuming you did not learn that hyperlink, the essential structure of a C program is a perform known as major, and its signature appears like this:

   int major(int argc, char **argv)
   

This should not look too unusual to a Python programmer. C capabilities have a return sort first, a perform identify, after which the typed arguments contained in the parenthesis. Last, the physique of the perform resides between the curly braces. The perform identify major is how the runtime linker (this system that constructs and runs applications) decides the place to start out executing your program. If you write a C program and it would not embody a perform named major, it won’t do something. Sad.

The perform argument variables argc and argv collectively describe a listing of strings which are typed by the consumer on the command line when this system is invoked. In the everyday terse Unix naming custom, argc means argument depend and argv means argument vector. Vector sounds cooler than record, and argl feels like a strangled cry for assist. We are Unix system programmers, and we don’t cry for assist. We make different folks cry for assist.

Moving on

$ ./myprog foo bar -x baz

If myprog is applied in C, argc could have the worth 5 and argv will likely be an array of tips to characters with 5 entries. (Don’t fear if that sounds super-technical; it is a record of 5 strings.) The first entry within the vector, argv[0], is the identify of this system. The remainder of argv accommodates the arguments:

   argv[zero] == "./myprog"
   argv[1] == "foo"
   argv[2] == "bar"
   argv[three] == "-x"
   argv[four] == "baz"
   
   /* Note: not legitimate C */

In C, you may have many selections to deal with the strings in argv. You may loop over the array argv manually and interpret every of the strings in keeping with the wants of this system. This is comparatively straightforward, however it results in applications with wildly totally different interfaces, as totally different programmers have totally different concepts about what’s “good.”

embody <stdio.h>

/* A easy C program that prints the contents of argv */

int major(int argc, char **argv)

Early makes an attempt to standardize the command line

The subsequent weapon within the command-line arsenal is a C standard library perform known as getopt. This perform permits the programmer to parse switches, arguments with a splash previous it, like -x, and optionally pair follow-on arguments with their switches. Think about command invocations like “/bin/ls -alSh"getopt is the perform initially used to parse that argument string. Using getopt makes parsing the command line fairly straightforward and improves the consumer expertise (UX).

#embody <stdio.h>
#embody <getopt.h>

#outline OPTSTR "b:f:"

extern char *optarg;

int major(int argc, char **argv)
    int choose;
    char *bar = NULL;
    char *foo = NULL;
   
    whereas((choose=getopt(argc, argv, OPTSTR)) != EOF)
       change(choose)
    printf("%sn", foo ? foo : "Empty foo");
    printf("%sn", bar ? bar : "Empty bar");

On a private observe, I want Python had changees, however that can never, ever happen.

The GNU era

The GNU undertaking got here alongside and launched longer format arguments for his or her implementations of conventional Unix command-line instruments, issues like --file-format foo. Of course, we Unix programmers hated that as a result of it was an excessive amount of to sort, however just like the dinosaurs we’re, we misplaced as a result of the customers appreciated the longer choices. I by no means wrote any code utilizing the GNU-style possibility parsing, so no code instance right here.

GNU-style arguments additionally settle for brief names like -f foo that should be supported, too. All of this alternative resulted in additional workload for the programmer who simply needed to know what the consumer was asking for and get on with it. But the consumer acquired an much more constant UX: lengthy and brief format choices and routinely generated assist that usually saved the consumer from trying to learn infamously difficult-to-parse manual pages (see ps for a very egregious instance).

But we’re speaking about Python?

You have now been uncovered to sufficient (an excessive amount of?) command-line historical past to have some context about the right way to strategy writing CLIs with our favourite language. Python offers an analogous variety of selections for command-line parsing; do it your self, a batteries-included possibility, and a plethora of third-party choices. Which one you select will depend on your explicit circumstances and wishes.

First, do it your self

You can get your program’s arguments from the sys module.

import sys

if __name__ == '__main__':
   for worth in sys.argv:
       print(worth)

Batteries included

There have been a number of implementations of argument-parsing modules within the Python commonplace library; getopt, optparse, and most just lately, argparse. Argparse permits the programmer to offer the consumer with a constant and useful UX, however like its GNU antecedents, it takes numerous work and “boilerplate code” on the a part of the programmer to make it “good.”

from argparse import ArgumentParser

if __name__ == "__main__":

   argparser = ArgumentParser(description='My Cool Program')
   argparser.add_argument("--foo", "-f", assist="A user supplied foo")
   argparser.add_argument("--bar", "-b", assist="A user supplied bar")
   
   outcomes = argparser.parse_args()
   print(outcomes.foo, outcomes.bar)

The payoff is routinely generated assist obtainable when the consumer invokes --help. But what concerning the benefit of batteries included? Sometimes the circumstances of your undertaking dictate that you’ve restricted or no entry to third-party libraries, and you must “make do” with the Python commonplace library.

A contemporary strategy to CLIs

And then there was Click. The Click framework makes use of a decorator strategy to constructing command-line parsing. All of a sudden, it is enjoyable and simple to put in writing a wealthy command-line interface. Much of the complexity melts away beneath the cool and futuristic use of decorators, and customers marvel on the automated help for key phrase completion in addition to contextual assist. All whereas writing much less code than earlier options. Anytime you possibly can write much less code and nonetheless get issues finished is a win. And all of us need wins.

import click on

@click on.command()
@click on.possibility("-f", "--foo", default="foo", assist="User supplied foo.")
@click on.possibility("-b", "--bar", default="bar", assist="User supplied bar.")
def echo(foo, bar):
    """My Cool Program
   
    It does stuff. Here is the documentation for it.
    """

    print(foo, bar)
   
if __name__ == "__main__":
    echo()

You can see among the similar boilerplate code within the @click on.possibility decorator as you noticed with argparse. But the “work” of making and managing the argument parser has been abstracted away. Now the perform echo is named magically with the command-line arguments parsed and the values assigned to the perform arguments.

Adding arguments to a Click interface is as straightforward as including one other decorator to the stack and including the brand new argument to the perform definition.

But wait, there’s extra!

Built on prime of Click, Typer is a good newer CLI framework that mixes the performance of Click with fashionable Python type hinting. One of the drawbacks of utilizing Click is the stack of decorators that should be added to a perform. CLI arguments should be laid out in two locations: the decorator and the perform argument record. Typer DRYs out CLI specs, leading to code that is simpler to learn and preserve.

import typer

cli = typer.Typer()

@cli.command()
def echo(foo: str = "foo", bar: str = "bar"):
    """My Cool Program
   
    It does stuff. Here is the documentation for it.
    """

    print(foo, bar)
   
if __name__ == "__main__":
    cli()

Time to start out writing some code

Which one in every of these approaches is correct? It will depend on your use case. Are you writing a fast and soiled script that solely you’ll use? Use sys.argv instantly and drive on. Do you want extra strong command-line parsing? Maybe argparse is sufficient. Do you may have a lot of subcommands and complex choices, and is your workforce going to make use of it every day? Now it’s best to undoubtedly take into account Click or Typer. Part of the enjoyable of being a programmer is hacking out alternate implementations to see which one fits you greatest.

Finally, there are many third-party packages for parsing command-line arguments in Python. I’ve solely offered those I like or have used. It is solely fantastic and anticipated so that you can like and/or use totally different packages. My recommendation is to start out with these and see the place you find yourself.

Go write one thing cool.


This article initially appeared on PyBites and is republished with permission.

Most Popular

To Top