Standard Library Quiz
Quiz
f yields lines efficiently. Using f.read() or f.readline() would iterate over characters, not lines.f yields lines efficiently. Using f.read() or f.readline() would iterate over characters, not lines.with open(), the file is automatically closed even if an exception occurs.with statement implements context management, ensuring the file is properly closed regardless of whether the code block exits normally or via an exception.with statement implements context management, ensuring the file is properly closed regardless of whether the code block exits normally or via an exception.with open("test.txt", "w") as f:
f.write("Hello\n")
with open("test.txt", "a") as f:
f.write("World\n")
# File contains:w writes “Hello” (truncating any existing content), then mode a appends “World” without truncating, resulting in both lines.w writes “Hello” (truncating any existing content), then mode a appends “World” without truncating, resulting in both lines.w, a, and w+ will create the file if it doesn’t exist. Mode r and r+ require the file to exist.w, a, and w+ will create the file if it doesn’t exist. Mode r and r+ require the file to exist.json.load() method reads from a file object and converts JSON to a Python dict. Note: json.loads() works with strings.json.load() method reads from a file object and converts JSON to a Python dict. Note: json.loads() works with strings.json.load() and json.loads()?json.load() and json.loads()?json.load() - Reads from a file object
json.loads() - Reads from a string
Remember: the ’s’ in loads() stands for ‘string’!
Note: Both functions convert JSON into Python objects (not just dicts) โ JSON objectsโdict, arraysโlist, stringsโstr, numbersโint/float, true/falseโTrue/False, nullโNone
Did you get it right?
import _____
import os
with _____.NamedTemporaryFile('w', delete=False) as tf:
tf.write(modified_content)
temp_name = tf.name
os.replace(temp_name, 'config.yaml')tempfile module provides NamedTemporaryFile for safe atomic file modifications.tempfile module provides NamedTemporaryFile for safe atomic file modifications.os.path.join('folder', 'subfolder', 'file.txt') ensure?os.path.join() combines path components using the correct separator for the current OS (e.g., / on Linux/Mac, \ on Windows). It doesn’t check existence or create files.os.path.join() combines path components using the correct separator for the current OS (e.g., / on Linux/Mac, \ on Windows). It doesn’t check existence or create files.['ls', '-l'] for safety, or strings with shell=True for shell features. ['ls -l'] treats the entire string as one command name.['ls', '-l'] for safety, or strings with shell=True for shell features. ['ls -l'] treats the entire string as one command name.shell=True in subprocess is generally safer than passing a list of arguments.shell=True is generally LESS safe because it can be vulnerable to shell injection attacks. Passing a list like ['ls', '-l'] is the recommended, safer approach.shell=True is generally LESS safe because it can be vulnerable to shell injection attacks. Passing a list like ['ls', '-l'] is the recommended, safer approach.from collections import Counter
counts = Counter(['a', 'b', 'a', 'c', 'b', 'a'])
print(counts['a'], counts['missing'])3 0.3 0.defaultdict(list) over a regular dict?defaultdict(list) is automatic initialization - accessing a new key creates an empty list, avoiding KeyError and eliminating manual initialization.defaultdict(list) is automatic initialization - accessing a new key creates an empty list, avoiding KeyError and eliminating manual initialization.defaultdict(int) initializes missing keys to 0, since int() returns 0. This is perfect for counting operations.defaultdict(int) initializes missing keys to 0, since int() returns 0. This is perfect for counting operations.Readability and Self-Documentation
user[0] vs user.name - the second is much clearer!
namedtuple provides:
- Named field access (
p.nameinstead ofp[0]) - Still works like a tuple (indexing, unpacking)
- Immutable like tuples
- More memory-efficient than classes or dicts
Did you get it right?
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age'])
p = Person('Alice', 30)
print(p[0], p.name)p[0]) and attribute access (p.name). Both refer to the same field, so both print ‘Alice’.p[0]) and attribute access (p.name). Both refer to the same field, so both print ‘Alice’.deque(maxlen=n) automatically removes the oldest item when the buffer is full and a new item is added, making it perfect for fixed-size circular buffers.deque(maxlen=n) automatically removes the oldest item when the buffer is full and a new item is added, making it perfect for fixed-size circular buffers.logging.exception() instead of logging.error()?logging.exception() should be used inside except blocks when you want to automatically capture and log the full stack trace. It’s equivalent to logging.error() but includes traceback.logging.exception() should be used inside except blocks when you want to automatically capture and log the full stack trace. It’s equivalent to logging.error() but includes traceback.logging.basicConfig() in every module that uses logging.basicConfig() ONLY ONCE in your main entry point. In libraries and modules, use logger = logging.getLogger(__name__) instead. basicConfig() only works the first time it’s called.basicConfig() ONLY ONCE in your main entry point. In libraries and modules, use logger = logging.getLogger(__name__) instead. basicConfig() only works the first time it’s called.import logging
logging.basicConfig(
level=logging.INFO,
handlers=[
logging.FileHandler('app.log'),
logging._____()
]
)StreamHandler() writes logs to console (stderr by default). Combined with FileHandler, this sends logs to both file and console.StreamHandler() writes logs to console (stderr by default). Combined with FileHandler, this sends logs to both file and console.datetime.now() and datetime.now(timezone.utc)?datetime.now() returns the current local time (naive datetime), while datetime.now(timezone.utc) returns UTC time with timezone information (aware datetime).datetime.now() returns the current local time (naive datetime), while datetime.now(timezone.utc) returns UTC time with timezone information (aware datetime).from datetime import datetime, timedelta
dt = datetime(2025, 1, 1)
future = dt + timedelta(days=7)
print((future - dt).days).days attribute gives the number of days, which is 7..days attribute gives the number of days, which is 7.time module vs the datetime module?time module vs the datetime module?time module: Low-level time operations
- Performance measurement (
time.perf_counter()) - Delays and waiting (
time.sleep()) - Unix timestamps (seconds since epoch)
- System clock access
datetime module: High-level date/time handling
- Business logic
- User-facing timestamps
- Calendar operations
- Date/time arithmetic (
timedelta) - Parsing and formatting dates
Rule of thumb: Use datetime for most application code; use time for performance measurement and delays.
Did you get it right?
re.search() return if no match is found?re.search() returns None if no match is found. This is why you should always check if match: before calling match.group().re.search() returns None if no match is found. This is why you should always check if match: before calling match.group().import re
text = 'Error on line 42: failed after 3 attempts'
match = re.search(r'\d+', text)
if match:
print(match.group())re.search() finds the FIRST match anywhere in the string. The first number encountered is 42, so that’s what match.group() returns.re.search() finds the FIRST match anywhere in the string. The first number encountered is 42, so that’s what match.group() returns.r'...' are recommended for regex to avoid backslash escaping issues. r'\d+' and r'\w+@\w+\.com' correctly use raw strings. '\\d+' works but requires double backslashes.r'...' are recommended for regex to avoid backslash escaping issues. r'\d+' and r'\w+@\w+\.com' correctly use raw strings. '\\d+' works but requires double backslashes.re.findall() returns a list of all matches. For example, re.findall(r'\d+', 'a1b2c3') returns ['1', '2', '3'].re.findall() returns a list of all matches. For example, re.findall(r'\d+', 'a1b2c3') returns ['1', '2', '3'].import re
pattern = ___
match = re.search(pattern, '2025-01-26')
print(match.group('year'), match.group('month'), match.group('day'))(?P<name>...) where name is the identifier you’ll use with match.group('name') or match.groupdict().
Answer: r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'(?P<name>...) where name is the identifier you’ll use with match.group('name') or match.groupdict().
Answer: r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'r'^\d+$' will match ‘123abc’.^ anchors to the start and $ anchors to the end, so ^\d+$ requires the ENTIRE string to be digits. ‘123abc’ has letters, so it won’t match.^ anchors to the start and $ anchors to the end, so ^\d+$ requires the ENTIRE string to be digits. ‘123abc’ has letters, so it won’t match.action='store_true' do?action='store_true' creates a flag that doesn’t require a value. If --verbose is present, args.verbose is True; if absent, it’s False.action='store_true' creates a flag that doesn’t require a value. If --verbose is present, args.verbose is True; if absent, it’s False.nargs='*' accepts zero or more arguments, and nargs='?' accepts zero or one. nargs='+' requires at least one, and nargs=3 requires exactly 3.nargs='*' accepts zero or more arguments, and nargs='?' accepts zero or one. nargs='+' requires at least one, and nargs=3 requires exactly 3.from pathlib import Path
p = Path('/home/user/file.txt')
print(p.stem).stem attribute returns the filename without the extension. .name would return ‘file.txt’, .suffix would return ‘.txt’, .parent would return ‘/home/user’..stem attribute returns the filename without the extension. .name would return ‘file.txt’, .suffix would return ‘.txt’, .parent would return ‘/home/user’.Readable, maintainable path operations
os.path: Verbose string manipulation (paths are strings)
os.path.join(os.path.expanduser('~'), '.config', 'myapp', 'settings.json')pathlib: Clean, chainable, intuitive (paths are objects)
Path.home() / '.config' / 'myapp' / 'settings.json'Paths become objects with methods, not strings with functions.
Did you get it right?
re.compile()?re.compile() improves performance when you use the same pattern multiple times, as the pattern is compiled once and reused.re.compile() improves performance when you use the same pattern multiple times, as the pattern is compiled once and reused.r'<div>.*</div>' with .* is greedy by default..* is greedy and matches as much as possible. To make it non-greedy (match as little as possible), use .*?..* is greedy and matches as much as possible. To make it non-greedy (match as little as possible), use .*?.from pathlib import Path
home_dir = Path._____Path.home() returns a Path object representing the user’s home directory, similar to os.path.expanduser('~').Path.home() returns a Path object representing the user’s home directory, similar to os.path.expanduser('~').most_common(n) method, and can count any hashable objects.most_common(n) method, and can count any hashable objects.datetime.strftime() and datetime.strptime()?strftime() converts datetime โ string (format time), while strptime() converts string โ datetime (parse time). Remember: ‘f’ for format out, ‘p’ for parse in.strftime() converts datetime โ string (format time), while strptime() converts string โ datetime (parse time). Remember: ‘f’ for format out, ‘p’ for parse in.from collections import deque
dq = deque([1, 2, 3], maxlen=3)
dq.append(4)
dq.append(5)
print(list(dq))maxlen=3, the deque automatically removes the oldest items when full. Adding 4 removes 1, adding 5 removes 2, leaving [3, 4, 5].maxlen=3, the deque automatically removes the oldest items when full. Adding 4 removes 1, adding 5 removes 2, leaving [3, 4, 5].check=True in subprocess.run() raises CalledProcessError if the command returns a non-zero exit code.check=True in subprocess.run() raises CalledProcessError if the command returns a non-zero exit code.Path.mkdir(parents=True, exist_ok=True) will raise an error if the directory already exists.exist_ok=True, no error is raised if the directory exists. parents=True creates parent directories as needed. This is a safe, idempotent operation.exist_ok=True, no error is raised if the directory exists. parents=True creates parent directories as needed. This is a safe, idempotent operation.subprocess.run(cmd, capture_output=True, text=True)["ls", "-l"]subprocessresult.stdout, result.stderr, result.returncodesubprocess.CalledProcessError in a try/exceptcheck=True means a non-zero returncode automatically raises CalledProcessError.check=True means a non-zero returncode automatically raises CalledProcessError.subprocess.run() return?subprocess.run() returns a CompletedProcess object. You access output via result.stdout, errors via result.stderr, and the exit code via result.returncode. Output is only available if you pass capture_output=True.subprocess.run() returns a CompletedProcess object. You access output via result.stdout, errors via result.stderr, and the exit code via result.returncode. Output is only available if you pass capture_output=True.subprocess.run() parameters do you need to capture command output as Python strings (not bytes)?capture_output=True redirects stdout and stderr so they’re stored in the result (otherwise they go directly to the terminal). text=True decodes the captured bytes into strings. Without text=True, result.stdout would be b'hello\n' instead of 'hello\n'.capture_output=True redirects stdout and stderr so they’re stored in the result (otherwise they go directly to the terminal). text=True decodes the captured bytes into strings. Without text=True, result.stdout would be b'hello\n' instead of 'hello\n'.result.returncode print for a successful command?import subprocess
result = subprocess.run(["echo", "hello"], capture_output=True, text=True)
print(result.returncode)0 means success in Unix convention. Non-zero values indicate failure โ this is the convention that check=True relies on to decide whether to raise CalledProcessError.0 means success in Unix convention. Non-zero values indicate failure โ this is the convention that check=True relies on to decide whether to raise CalledProcessError.import subprocess
try:
result = subprocess.run(
["ls", "/fake/path"],
capture_output=True,
text=True,
check=True
)
print(result.stdout)
except subprocess._____:
print("Command failed!")subprocess.CalledProcessError is raised when check=True and the command returns a non-zero exit code. The exception object has .returncode, .cmd, .stdout, and .stderr attributes for debugging.subprocess.CalledProcessError is raised when check=True and the command returns a non-zero exit code. The exception object has .returncode, .cmd, .stdout, and .stderr attributes for debugging.ArgumentParser(description='...')args.filename, args.verboseargparse'filename') and optional ('--verbose')args = parser.parse_args()parser.add_argument('filename')) are matched by position and required by default. Optional arguments (parser.add_argument('--output')) use the -- prefix and are optional by default. This naming convention is the core design of argparse.parser.add_argument('filename')) are matched by position and required by default. Optional arguments (parser.add_argument('--output')) use the -- prefix and are optional by default. This naming convention is the core design of argparse.--.import argparse
parser = argparse.ArgumentParser(description='Process files')
parser.add_argument('filename', help='Input file')
parser.add_argument('--verbose', action='store_true')
args = parser._____
print(args.filename)parser.parse_args() reads sys.argv, validates inputs against defined arguments, and returns a Namespace object. Accessing args.filename and args.verbose is only possible after this call.parser.parse_args() reads sys.argv, validates inputs against defined arguments, and returns a Namespace object. Accessing args.filename and args.verbose is only possible after this call.logger = logging.getLogger(__name__) instead of calling logging.info() directly?logging.getLogger(__name__) creates a logger named after the module (e.g., myapp.database), which identifies the source in log output. Crucially, modules should never call basicConfig() โ only the entry point does. Module loggers inherit configuration from the root logger set up by basicConfig().logging.getLogger(__name__) creates a logger named after the module (e.g., myapp.database), which identifies the source in log output. Crucially, modules should never call basicConfig() โ only the entry point does. Module loggers inherit configuration from the root logger set up by basicConfig().FileNotFoundError is the specific exception raised when a file doesn’t exist. While OSError (and its alias IOError) would also catch it as a parent class, catching FileNotFoundError specifically makes your intent clear and avoids masking unrelated OS errors.FileNotFoundError is the specific exception raised when a file doesn’t exist. While OSError (and its alias IOError) would also catch it as a parent class, catching FileNotFoundError specifically makes your intent clear and avoids masking unrelated OS errors.r, w, a, r+)FileNotFoundError, PermissionError)with open(filename, mode) as fjson, tempfile, etc.)with open() (ensures cleanup) โ pick mode appropriate for your operation โ read/write โ wrap in try/except for error handling. The with statement guarantees the file is closed even if an exception occurs.with open() (ensures cleanup) โ pick mode appropriate for your operation โ read/write โ wrap in try/except for error handling. The with statement guarantees the file is closed even if an exception occurs.logging.basicConfig() once at the entry point (set level, format, handlers)logger = logging.getLogger(__name__)loggingdebug, info, warning, error, critical)logging.exception() inside except blocks to capture stack tracesos module:os.listdir, os.path.exists, os.path.isfile)os.getenv, os.environ)osos.path.join)os.getcwd, os.chdir)mkdir, remove, replace)if match: before calling .group() to avoid AttributeErrorresearch(), match(), findall(), or sub()r'...' to avoid backslash issuesre.compile() when reusing the pattern multiple timesdatetime:timedelta (add or subtract durations)datetime, timedelta, and timezone as neededstrftime() or parse a string with strptime()datetime.now(), fromisoformat(), strptime())timezone.utc when timezone-awareness is required