Most of my scripts start life taking one argument, and the first version always looks the same: sys.argv[1] and a prayer. That's fine on the day I write it and incomprehensible six months later, when I've forgotten the argument order and there's no help to be had. Here's a real script — tail the last lines of a log file — being dragged into the argparse era.
sys.argv[1]# taillog.py — first version
import sys
with open(sys.argv[1], encoding="utf-8") as f:
lines = f.readlines()
for line in lines[-10:]:
print(line, end="")
It works. The hidden costs: run it with no argument and you get a raw IndexError traceback instead of a hint; the 10 is buried in the code where only reading the source will find it; and there is no way to discover how to use the script except reading the source. All three of these are really the same problem — the command line isn't defined anywhere, it just happens.
# taillog.py — second version
import argparse
from pathlib import Path
parser = argparse.ArgumentParser(description="Print the last lines of a file.")
parser.add_argument("filename", help="the file to read")
parser.add_argument("-n", "--count", type=int, default=10,
help="how many lines to show (default: 10)")
parser.add_argument("--verbose", action="store_true",
help="also show the total line count")
args = parser.parse_args()
lines = Path(args.filename).read_text(encoding="utf-8").splitlines()
start = max(len(lines) - args.count, 0)
if args.verbose:
print(f"{len(lines)} lines total")
for line in lines[start:]:
print(line)
What each piece buys:
filename — a positional argument with a help string, so it's defined rather than implied.-n / --count — type=int converts and validates: python taillog.py server.log -n abc fails with a clear one-line message instead of a TypeError two lines in. default=10 means the common case takes no flags at all, and both -n 5 and --count 5 work.--verbose — action="store_true" is a flag that defaults to False and becomes True when present.-h for freeusage: taillog.py [-h] [-n COUNT] [--verbose] filename
Print the last lines of a file.
positional arguments:
filename the file to read
options:
-h, --help show this help message and exit
-n COUNT, --count COUNT
how many lines to show (default: 10)
--verbose also show the total line count
That entire screen is generated from the six add_argument lines — I wrote none of it. Six months from now, my future self runs taillog.py -h and knows everything the script can do.
The slice looks like it should be lines[-args.count:], and it almost is — until someone passes -n 0, because lines[-0:] means "from zero steps before the end", which is the whole file. The max(len(lines) - args.count, 0) version behaves for every count, including zero. I found this out by passing 0 on purpose, which is a habit I recommend.
argparse is wordy — six lines of setup for a two-flag script feels like overhead, and for a truly throwaway script, skip it. But the help text and the validation are precisely the parts I never write by hand and precisely the parts my future self needs. Ten minutes per script, once, and the script stops being a note to self.