Python

Command-line arguments and environment variables — argparse and os.environ

A real script rarely has its input hardcoded — it reads from sys.argv, a properly parsed set of --flags via argparse, or os.environ, and knowing which one fits which kind of input is most of the actual skill here.

Intermediate

3 min read

sys.argv: the raw, unparsed argument list

# script.py
import sys
print(sys.argv)
 
# python script.py input.csv --verbose
# ['script.py', 'input.csv', '--verbose']

sys.argv is a plain list of strings — the command that launched the script, split on whitespace, with no parsing, no type conversion, and no validation at all. sys.argv[0] is always the script's own name; everything after that is whatever was actually typed. This is the raw material argparse is built on top of — reaching for sys.argv directly is fine for a one-off script with a single positional argument, but it stops scaling the moment there's more than one optional flag.

argparse: named flags, types, help text, and validation — for free

import argparse
 
parser = argparse.ArgumentParser(description="Process a CSV report.")
parser.add_argument("input_file", help="path to the CSV file")
parser.add_argument("--verbose", action="store_true", help="print detailed progress")
parser.add_argument("--limit", type=int, default=100, help="max rows to process")
 
args = parser.parse_args()
print(args.input_file, args.verbose, args.limit)
 
# python script.py report.csv --limit 50
# report.csv False 50

add_argument("input_file", ...) (no leading dashes) defines a positional argument — required, matched by position. add_argument("--verbose", ...) (with dashes) defines an optional flag. type=int converts and validates the value automatically — passing --limit abc fails with a clear, automatically-generated error message, not a ValueError buried somewhere later in the script. action="store_true" makes a flag a boolean switch: present means True, absent means False, with no value to type after it.

argparse also generates a working --help flag automatically from the descriptions passed to add_argument — running python script.py --help prints usage text without an extra line of code, which is one of the concrete reasons to reach for it even for a script with just two or three options.

os.environ: configuration that lives outside the code entirely

import os
 
api_key = os.environ["API_KEY"]           # KeyError if it's genuinely missing — loud, not silent
api_key = os.environ.get("API_KEY")         # None if missing — safe lookup, mirrors dict.get()
debug = os.environ.get("DEBUG", "false") == "true"   # env vars are ALWAYS strings — compare as strings

os.environ is a dict-like object holding every environment variable the process was launched with — the standard place for configuration that shouldn't be hardcoded or committed to source control (API keys, database URLs, which environment — dev/staging/prod — the code is running in). Every value in it is a string, even something conceptually boolean or numeric — os.environ.get("PORT") returns "8000", not 8000, and comparing an env var to the string "true" rather than the boolean True is a common early mistake.

Choosing between the three

# sys.argv:      quick one-off script, a single simple positional argument
# argparse:       multiple named flags, types, help text, real CLI tools
# os.environ:      secrets, per-environment config, anything that shouldn't be
#                   typed at the command line every single run or committed to git

The real distinction is about who provides the value and how often: a command-line argument is something a person types deliberately, per invocation (which file to process, this run); an environment variable is something set once in the deployment environment and read implicitly, every run, without anyone typing it (a database URL, an API key) — mixing these up means either re-typing a secret on every command, or hardcoding something that should change per-run into a fixed environment setting.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What does sys.argv contain?

2. What does action='store_true' do in argparse.add_argument?

3. Why does os.environ.get('DEBUG', 'false') == 'true' compare against strings, not booleans?

4. What kind of input is os.environ the right tool for, that a CLI flag usually isn't?