Addaly is in open beta. Things will change, and AI answers can be wrong — check anything that matters.

Python, From Zero, For AI

From your first line of code to your first API call.

Lesson 29 of 898 min

Giving your script a proper command line

input() is the wrong tool for a script

A program that asks questions with input() cannot be scheduled, cannot be run by another program, and cannot be put in a pipeline. The moment a script is useful, somebody wants to run it on a timer or on fifty files, and interactive prompts stop that dead.

The values a run needs should arrive on the command line.

The crude version

python
import sys

path = sys.argv[1]
print(f"processing {path}")
bash
python3 clean.py data/sales.csv

sys.argv is a list of strings. Position 0 is the script name, so real arguments start at 1. Everything is a string, including numbers.

This works and it stops working the moment you have two options, or an optional one, or want --help. Then you are writing an argument parser by hand, badly.

argparse, which ships with Python

python
import argparse

def main():
    parser = argparse.ArgumentParser(description="Clean a sales export.")
    parser.add_argument("path", help="input CSV")
    parser.add_argument("--out", default="clean.csv", help="output path")
    parser.add_argument("--limit", type=int, help="stop after N rows")
    parser.add_argument("--dry-run", action="store_true",
                        help="report what would change, write nothing")
    args = parser.parse_args()

    print(args.path, args.out, args.limit, args.dry_run)

if __name__ == "__main__":
    main()

That is the whole of it, and it gives you a great deal for fifteen lines:

bash
python3 clean.py data/sales.csv --limit 100 --dry-run
python3 clean.py --help
  • type=int converts and rejects non-numbers with a clear message, rather than crashing later.
  • action="store_true" makes a flag that is on when present, off when absent.
  • A missing required argument prints usage and exits with status 2, without a traceback.
  • --help is generated from what you already wrote. Nobody has to maintain it separately.
  • --dry-run in a hyphenated form becomes args.dry_run.

A --dry-run flag on anything that writes, deletes or sends is worth building in from the start. It is the difference between testing on production data and destroying it.

Exit codes, because other programs are listening

python
import sys

if not path.exists():
    print(f"no such file: {path}", file=sys.stderr)
    sys.exit(1)

By convention, exit status 0 means success and anything else means failure. Shell scripts, CI systems and cron all read it. A script that prints "ERROR" and exits 0 will be treated as a success by everything that automates it.

Two details in that snippet. Errors go to stderr, not stdout, so that python3 clean.py > out.csv still shows you the error instead of burying it in the file. And an uncaught exception exits with status 1 automatically, which is fine — you do not need to wrap everything in try to get correct exit behaviour.

Reading from standard input

bash
cat sales.csv | python3 clean.py -

Treating - as "read stdin" makes your script composable with every other command-line tool. sys.stdin behaves like an open file, so a loop over it works line by line:

python
source = sys.stdin if args.path == "-" else open(args.path)

Restricting and grouping options

python
parser.add_argument("--format", choices=["csv", "json"], default="csv")
parser.add_argument("--verbose", "-v", action="count", default=0)
parser.add_argument("files", nargs="+", help="one or more inputs")

choices rejects anything else with a message naming the valid values, so a typo fails at the door instead of three functions later. action="count" gives the familiar -v, -vv, -vvv pattern, which maps neatly onto logging levels. nargs="+" accepts many values, which is what lets the shell expand *.csv into a list before your script ever sees it.

For a tool that grows several distinct jobs, parser.add_subparsers() gives you mytool clean and mytool report with separate options each, in the style of git. Reach for it when a single flat option list starts having options that only make sense together.

The alternatives, and whether you need them

click and typer are free third-party libraries that build the parser from a function signature and type hints, which is genuinely nicer for a complex tool with subcommands. argparse needs no installation, works everywhere, and handles everything up to moderate complexity.

For a script somebody else has to install, prefer the standard library. Every dependency is a thing that must be present on the machine where the script eventually runs, and that machine is often not yours.

Try this now

Take a script you wrote earlier that has a hard-coded file path. Give it an argparse interface with the path as a positional argument, a --limit with type=int, and a --dry-run. Then run it with --help and read what you get for free, and run it with a missing file to check the exit code with echo $?.

The one thing to keep

Arguments belong on the command line rather than in `input()` prompts, and `argparse` gives you conversion, validation, generated help and correct exit codes for about fifteen lines.

Before you move on

A nightly job runs `python3 clean.py` and the operations team reports it as green every night, though the output file has been empty for a week. The script prints "ERROR: source missing" and stops. Why did nothing notice?

Pick the one you would defend. Nobody sees your answer.

No ads. No data sale. No public scores on people. Ever.

© 2026 Addaly