Python Standard Library Essentials: datetime, collections and json
AI-assistedDrafted with AI assistance and reviewed before publishing; examples are version-pinned. Verify against official docs when in doubt.
Python ships with a large standard library. This article covers the handful of modules you will import almost every day: time, specialized containers, JSON, environment access, subprocesses, randomness and file copying.
Environment
Examples are verified on Python 3.13 and use only built-in modules — nothing to install.
datetime: dates and times
from datetime import date, datetime, timedelta, timezone
today = date.today() # 2026-11-03
now = datetime.now() # local time, naive (no tzinfo)
utc = datetime.now(timezone.utc) # aware
Arithmetic uses timedelta:
tomorrow = today + timedelta(days=1)
last_week = now - timedelta(weeks=1)
diff = datetime(2026, 12, 1) - now # timedelta
diff.days # whole days between the two
Formatting and parsing:
now.strftime("%Y-%m-%d %H:%M:%S") # '2026-11-03 14:30:00'
now.isoformat(timespec="seconds") # '2026-11-03T14:30:00'
datetime.strptime("2026-11-03", "%Y-%m-%d") # parse with an explicit format
datetime.fromisoformat("2026-11-03T14:30:00") # parse ISO 8601
Time zones (use the built-in zoneinfo since 3.9, no pytz needed):
from zoneinfo import ZoneInfo
shanghai = datetime.now(ZoneInfo("Asia/Shanghai"))
utc_time = shanghai.astimezone(timezone.utc) # convert, don't relabel
Unix timestamps:
now.timestamp() # seconds as float
datetime.fromtimestamp(1762000000, tz=timezone.utc)
collections: better containers
Counter
from collections import Counter
words = "a b a c a b".split()
counter = Counter(words)
counter["a"] # 3
counter["zzz"] # 0, no KeyError
counter.most_common(2) # [('a', 3), ('b', 2)]
counter + Counter(["a"]) # element-wise addition
counter.update(["a", "d"])
defaultdict
from collections import defaultdict
groups = defaultdict(list) # a missing key creates an empty list
for name, team in [("a", "red"), ("b", "blue"), ("c", "red")]:
groups[team].append(name)
# {'red': ['a', 'c'], 'blue': ['b']}
Use defaultdict(int) for counting and defaultdict(dict) for nested structures. The factory takes no arguments; when you need a different default, pass lambda: 1.
deque
from collections import deque
queue = deque(["a", "b"])
queue.append("c") # push right
queue.popleft() # 'a' — O(1), unlike list.pop(0) which is O(n)
queue.appendleft("z")
queue.rotate(1)
history = deque(maxlen=3) # bounded: oldest items fall off automatically
namedtuple
from collections import namedtuple
Row = namedtuple("Row", ["id", "name"])
r = Row(1, "a")
r.name # 'a'
r._replace(id=2)
New code should prefer typing.NamedTuple, which supports annotations and defaults.
json: serialize and parse
import json
data = {"id": 1, "tags": ["py"], "active": True}
text = json.dumps(data, ensure_ascii=False, indent=2)
back = json.loads(text) # back == data
with open("config.json", encoding="utf-8") as f:
config = json.load(f)
with open("config.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
ensure_ascii=Falsekeeps non-ASCII text readable instead of\uXXXXescapes.json.loadsraisesjson.JSONDecodeErroron malformed input — catch it at the boundary where external data enters.- Types map to JSON primitives:
dict→ object,list/tuple→ array,None→null.datetimeand dataclasses are not serializable by default; add adefault=function (dataclasses.asdicthelps). - JSON has no comments and no trailing commas; a config file that works in Python will not parse as JSON.
- Duplicate keys are silently collapsed to the last occurrence — do not rely on order or duplicates.
os and sys: environment and process
import os, sys
os.getenv("API_KEY") # None when unset
os.getenv("PORT", "8000") # with a default
os.environ["API_KEY"] = "..." # affects only this process and its children
sys.argv # ['script.py', '--flag']; argv[0] is the script
sys.version_info # named tuple, comparable: sys.version_info >= (3, 11)
sys.exit(1) # exit code 1
Anything os.path does, pathlib does better in new code — see the file I/O article.
subprocess: run external commands
import subprocess
result = subprocess.run(
["git", "rev-parse", "HEAD"],
capture_output=True, text=True, check=True,
)
result.stdout.strip() # commit hash
- Pass arguments as a list. Avoid
shell=Truewith interpolated strings, which invites command injection. check=Trueturns a non-zero exit code intosubprocess.CalledProcessError(with.returncodeand.stderr).text=Truedecodes output tostr; otherwise you getbytes.
random and secrets
import random, secrets
random.choice(["a", "b"]) # one random element
random.sample(range(100), 5) # 5 distinct values
random.randint(1, 10) # inclusive on both ends
secrets.token_urlsafe(32) # cryptographically secure token
secrets.choice(["a", "b"])
random is a Mersenne Twister: fast, reproducible, and predictable. Anything that guards access — tokens, passwords, session IDs, password-reset links — must come from secrets.
shutil: copying and archives
import shutil
shutil.copy2("a.txt", "backup/a.txt") # copy contents plus metadata
shutil.move("a.txt", "archive/")
shutil.rmtree("tmp/") # recursive delete — no undo
shutil.make_archive("dist", "zip", "build/")
Common mistakes
1. Mixing naive and aware datetimes
Subtracting a naive datetime.now() from an aware datetime.now(timezone.utc) raises TypeError: can't subtract offset-naive and offset-aware datetimes. Pick one convention: store UTC aware values internally, convert to local time only for display.
2. Doing date math with time.time()
Seconds since the epoch ignore months, leap years and time zones. Use datetime plus timedelta.
3. Reading a defaultdict as if it were a dict
Accessing a missing key inserts it, so if key in d flips to True after the first read, and len(d) grows. Use d.get(key, default) when you only want to look.
4. Building shell strings for subprocess
subprocess.run(f"cat {filename}", shell=True) # breaks on spaces and metacharacters
Use ["cat", filename] instead; it also avoids quoting bugs entirely.
5. Generating tokens with random
random is seeded predictably and its output can be reconstructed from enough samples. Use secrets.
6. Treating json.load as validation
Parsing only proves the text is syntactically valid JSON. Check required keys and types yourself, or use a schema library, before trusting the data.
Summary
- Time:
datetime+timedelta,zoneinfofor zones, store UTC aware values. - Counting:
Counter; grouping:defaultdict; queues and sliding windows:deque. - JSON:
dumps/loadswithensure_ascii=False; decode errors surface asJSONDecodeError. - Config from
os.getenv, argv fromsys.argv(useargparsefor anything non-trivial). - External commands:
subprocess.run([...], check=True); tokens:secrets.
Next: regular expressions, the last tool in this intermediate set.