Q&A library Python

How do I write a Python script that runs from the command line?

Python Advanced argparsecliscripts

Short answer

Use argparse to define arguments, put the work in a main function, and call it under the name-main guard. This gives you help text, type conversion and validation for free.

The short version

Use argparse to declare the arguments your script accepts, put the real work in a main function, and call it under if __name__ == "__main__":. You get --help output, type conversion and error messages for free, instead of picking apart sys.argv by hand.

A complete small script

import argparse
import pandas as pd

def main():
    parser = argparse.ArgumentParser(description="Summarise orders by city.")
    parser.add_argument("input", help="path to the input CSV")
    parser.add_argument("--output", default="summary.csv", help="where to write")
    parser.add_argument("--minimum", type=float, default=0.0,
                        help="ignore orders below this amount")
    args = parser.parse_args()

    df = pd.read_csv(args.input)
    df = df[df["amount"] >= args.minimum]
    summary = df.groupby("city", as_index=False)["amount"].sum()
    summary.to_csv(args.output, index=False)
    print(f"wrote {len(summary)} rows to {args.output}")

if __name__ == "__main__":
    main()

Run it with python summarise.py orders.csv --minimum 10.

What argparse gives you

A positional argument (input) is required; anything starting with -- is optional and can have a default. type=float converts and validates the value, so a non-numeric input produces a clear error rather than a crash deep in your code. And --help is generated automatically from your help strings, so the script documents itself.

Why not just use sys.argv

import sys
path = sys.argv[1]        # crashes with an unhelpful IndexError if omitted
minimum = float(sys.argv[2])   # crashes if not a number, no useful message

This works until someone runs it wrongly, at which point they get an obscure traceback instead of "the following arguments are required: input". Every hand-rolled argument parser eventually reinvents a worse argparse.

Why the main function and the guard

Putting the work in main() and calling it under the guard means the script can also be imported (by a test, or another script) without running. That makes the logic testable, which a top-level script body is not.

Flags and choices

Two more options cover most needs: a boolean flag and a restricted set of values.

parser.add_argument("--verbose", action="store_true")
parser.add_argument("--mode", choices=["fast", "careful"], default="fast")

store_true makes --verbose a flag that needs no value, and choices rejects anything not in the list, with a helpful message.

Exit codes matter

If the script fails, exit with a non-zero status so that whatever runs it (a scheduler, a pipeline) knows it failed.

import sys
if df.empty:
    print("no rows to summarise", file=sys.stderr)
    sys.exit(1)

Printing errors to stderr and exiting non-zero is the convention that lets your script cooperate with other tools, rather than silently reporting success while doing nothing.

Need this solved on your own data?

The library is free and always will be. When the problem is bigger than an answer, we take on the work.

Start a project