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.
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 50add_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 stringsos.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 gitThe 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?