Skip to content

Python Common Modules

Python’s standard library includes a wealth of ready-to-use modules. This article introduces the ones most commonly needed in day-to-day development: serialization, path manipulation, time handling, system interfaces, logging, and more.

json — Cross-Language Serialization

JSON is a universal data exchange format supported by virtually every programming language. Python’s built-in json module converts between JSON and Python data structures:

import json

# Python → JSON string
data = {"name": "Alice", "age": 25, "scores": [90, 85, 92]}
text = json.dumps(data, ensure_ascii=False, indent=2)
print(text)
# {
#   "name": "Alice",
#   "age": 25,
#   "scores": [90, 85, 92]
# }

# JSON string → Python
parsed = json.loads(text)
print(parsed["name"])   # Alice

# write to file
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

# read from file
with open("data.json", encoding="utf-8") as f:
    loaded = json.load(f)

json supports these types: str, int, float, bool, list, dict, None. Custom objects require subclassing JSONEncoder:

import json
from datetime import datetime

class DateEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

data = {"created_at": datetime(2024, 6, 1, 12, 0)}
print(json.dumps(data, cls=DateEncoder))
# {"created_at": "2024-06-01T12:00:00"}
json vs pickle: JSON outputs a string, is cross-language compatible, but only supports basic types. pickle outputs bytes, is Python-only, and supports nearly every type (including custom classes and functions), but deserializing untrusted pickle data is a security risk.

pickle — Python-Specific Serialization

import pickle

class User:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

user = User("Bob", 30)

# serialize to bytes
data = pickle.dumps(user)

# deserialize from bytes
user2 = pickle.loads(data)
print(user2.name, user2.age)   # Bob 30

# write to / read from file (must use binary mode)
with open("user.pkl", "wb") as f:
    pickle.dump(user, f)

with open("user.pkl", "rb") as f:
    user3 = pickle.load(f)

pathlib — Modern Path Operations (Python 3.4+, Recommended)

pathlib.Path handles file paths in an object-oriented way and is the modern replacement for os.path:

from pathlib import Path

# create a path object (automatically adapts to the OS separator)
p = Path("/home/user/docs/readme.txt")
p = Path(".")   # current directory

# path joining (use the / operator)
config = Path("/etc") / "app" / "config.yaml"

# path information
p = Path("/home/user/docs/readme.txt")
print(p.name)        # readme.txt
print(p.stem)        # readme
print(p.suffix)      # .txt
print(p.parent)      # /home/user/docs
print(p.parts)       # ('/', 'home', 'user', 'docs', 'readme.txt')

# checks
print(p.exists())    # whether it exists
print(p.is_file())   # whether it is a file
print(p.is_dir())    # whether it is a directory

# read/write text (automatically handles open/close)
path = Path("hello.txt")
path.write_text("Hello, World!", encoding="utf-8")
content = path.read_text(encoding="utf-8")

# read/write bytes
path.write_bytes(b"\x00\x01\x02")
data = path.read_bytes()

# iterate over a directory
for f in Path(".").iterdir():
    print(f)

# glob pattern matching
for py_file in Path("src").glob("**/*.py"):  # recursively find all .py files
    print(py_file)

# create / delete
Path("new_dir").mkdir(parents=True, exist_ok=True)
Path("file.txt").unlink(missing_ok=True)  # delete a file

# get file size and modification time
stat = path.stat()
print(stat.st_size)   # size in bytes

os — Operating System Interface

import os

# environment variables
print(os.environ.get("PATH"))
os.environ["MY_VAR"] = "hello"

# current working directory
print(os.getcwd())
os.chdir("/tmp")

# run a system command
os.system("ls -la")   # does not capture output

# run a command and capture its output (subprocess is recommended)
import subprocess
result = subprocess.run(["ls", "-la"], capture_output=True, text=True)
print(result.stdout)

# file/directory operations
os.makedirs("a/b/c", exist_ok=True)   # create directories recursively
os.rename("old.txt", "new.txt")
os.remove("file.txt")
os.rmdir("empty_dir")

# list a directory
print(os.listdir("."))

# system info
print(os.name)    # 'posix' (Linux/Mac) or 'nt' (Windows)
print(os.sep)     # '/' or '\\'
print(os.getpid())  # current process ID
For path string manipulation, prefer pathlib.Path over os.path. os.path still works but produces more verbose code.

shutil — High-Level File Operations

import shutil

# copy a file (content only)
shutil.copyfile("src.txt", "dst.txt")

# copy a file (content + permissions)
shutil.copy("src.txt", "dst/")

# copy a file (content + permissions + metadata)
shutil.copy2("src.txt", "dst/")

# recursively copy a directory
shutil.copytree("src_dir", "dst_dir")   # dst_dir must not already exist

# move a file or directory
shutil.move("old_path", "new_path")

# recursively delete a directory (dangerous — double-check the path!)
shutil.rmtree("dir_to_delete")

# archive as zip/tar
shutil.make_archive("output", "zip", "source_dir")
shutil.make_archive("output", "gztar", "source_dir")

# extract
shutil.unpack_archive("output.zip", "extract_to/")

sys — Python Interpreter Interface

import sys

# command-line arguments (sys.argv[0] is the script name)
# running: python script.py arg1 arg2
print(sys.argv)   # ['script.py', 'arg1', 'arg2']

# Python version
print(sys.version)         # '3.12.0 (main, ...) [GCC ...]'
print(sys.version_info)    # sys.version_info(major=3, minor=12, ...)

# platform
print(sys.platform)   # 'linux' / 'darwin' / 'win32'

# module search path
print(sys.path)
sys.path.insert(0, "/my/custom/modules")  # insert a custom path at the front

# standard I/O
sys.stdout.write("Hello\n")
sys.stderr.write("Error message\n")

# exit the program (0 = normal, non-zero = error)
# sys.exit(0)

# recursion depth limit
print(sys.getrecursionlimit())    # 1000
sys.setrecursionlimit(2000)

# object memory size
import sys
lst = [1, 2, 3]
print(sys.getsizeof(lst))   # bytes

argparse — Command-Line Argument Parsing

When a script needs to accept command-line arguments, the standard library’s argparse is a more professional choice than manually parsing sys.argv — it auto-generates help text and validates argument types for you:

import argparse

parser = argparse.ArgumentParser(
    prog="mytool",
    description="An example command-line tool",
)

# Positional argument: required by default; nargs='?' + default makes it optional
parser.add_argument("path", nargs="?", default=".", help="Directory to process, defaults to the current directory")

# Optional argument: boolean flag
parser.add_argument("-a", "--all", action="store_true", help="Show hidden files starting with .")

# Optional argument: with type conversion and a default value
parser.add_argument("-n", "--limit", type=int, default=10, help="Maximum number of entries to show")

args = parser.parse_args()
print(args)
# Namespace(path='.', all=False, limit=10)
print(args.path, args.all, args.limit)
$ python mytool.py -h
usage: mytool [-h] [-a] [-n LIMIT] [path]

An example command-line tool

positional arguments:
  path                  Directory to process, defaults to the current directory

options:
  -h, --help            show this help message and exit
  -a, --all             Show hidden files starting with .
  -n LIMIT, --limit LIMIT
                        Maximum number of entries to show

$ python mytool.py /var/log -a -n 20
Namespace(path='/var/log', all=True, limit=20)

Commonly used parameters:

ParameterPurpose
nargsNumber of values: ? means 0 or 1, * means any number, + means at least 1
defaultValue used when the argument isn’t provided
typeAutomatic type conversion, e.g. type=int, type=float
choicesRestrict the argument to one of a fixed set of values
action="store_true"Boolean flag — True if present
destCustom attribute name for the parsed value on the Namespace
For subcommand-style tools (like git commit, git push — a “tool + subcommand” shape), parser.add_subparsers() lets you define separate arguments per subcommand. It’s the standard way to build more complex CLI tools.

datetime — Dates and Times

datetime is Python’s modern module for handling dates and times, and is easier to use than the time module:

from datetime import datetime, date, timedelta, timezone

# get the current time
now = datetime.now()             # local time (no timezone)
utcnow = datetime.now(timezone.utc)  # UTC time (recommended)

print(now)               # 2024-06-01 14:30:00.123456
print(now.year)          # 2024
print(now.strftime("%Y-%m-%d %H:%M:%S"))  # 2024-06-01 14:30:00

# parse a time string
dt = datetime.strptime("2024-06-01 14:30:00", "%Y-%m-%d %H:%M:%S")

# time arithmetic
tomorrow = now + timedelta(days=1)
last_week = now - timedelta(weeks=1)
diff = datetime(2024, 12, 31) - now
print(f"{diff.days} days until the end of the year")

# date only
today = date.today()
print(today.isoformat())   # 2024-06-01

# timestamp conversion
import time
ts = now.timestamp()           # datetime → timestamp (float)
dt2 = datetime.fromtimestamp(ts)  # timestamp → datetime

math — Mathematical Operations

import math

print(math.pi)            # 3.141592653589793
print(math.e)             # 2.718281828459045
print(math.tau)           # 6.283... (2π)

print(math.ceil(4.1))     # 5 (round up)
print(math.floor(4.9))    # 4 (round down)
print(math.trunc(4.9))    # 4 (truncate decimal part)

print(math.sqrt(16))      # 4.0
print(math.pow(2, 10))    # 1024.0
print(math.log(100, 10))  # 2.0 (base-10 logarithm)
print(math.log2(1024))    # 10.0
print(math.log10(1000))   # 3.0

print(math.fabs(-3.14))   # 3.14 (floating-point absolute value)
print(math.gcd(48, 18))   # 6 (greatest common divisor; Python 3.9+ supports multiple args)
print(math.lcm(4, 6))     # 12 (least common multiple, Python 3.9+)
print(math.factorial(10)) # 3628800

# trigonometric functions (arguments in radians)
print(math.sin(math.pi / 2))  # 1.0
print(math.degrees(math.pi))  # 180.0
print(math.radians(180))       # 3.14...

random — Random Numbers

import random

# basic random numbers
print(random.random())          # [0.0, 1.0)
print(random.uniform(1.5, 3.5)) # float in the given range
print(random.randint(1, 6))     # closed-interval integer [1, 6] (simulates a die roll)
print(random.randrange(0, 10, 2))  # one of 0, 2, 4, 6, 8

# sequence operations
items = ["apple", "banana", "cherry", "date"]
print(random.choice(items))          # pick one at random
print(random.choices(items, k=3))    # pick 3 with replacement (duplicates allowed)
print(random.sample(items, k=3))     # pick 3 without replacement (no duplicates)

shuffled = items.copy()
random.shuffle(shuffled)             # in-place shuffle
print(shuffled)

# set a random seed (for reproducible results)
random.seed(42)
print([random.randint(1, 100) for _ in range(5)])
# always the same result: depends on the implementation

logging — Logging

logging is more suitable for production code than print, providing log levels, formatting, and output destination control:

import logging

# five levels (lowest to highest): DEBUG < INFO < WARNING < ERROR < CRITICAL
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)

logger = logging.getLogger(__name__)   # named after the module

logger.debug("Debug info, only visible during development")
logger.info("Normal program execution record")
logger.warning("Warning: config item missing, using default value")
logger.error("Error: database connection failed")
logger.critical("Critical: system is about to crash")

Output to Both File and Console

import logging

logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)

# file handler
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.WARNING)   # only WARNING and above goes to the file

# console handler
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)   # all levels go to the console

# shared format
fmt = logging.Formatter("%(asctime)s [%(levelname)-8s] %(message)s")
file_handler.setFormatter(fmt)
stream_handler.setFormatter(fmt)

logger.addHandler(file_handler)
logger.addHandler(stream_handler)

logger.info("Server started")
logger.error("Connection timed out")

Logging Configuration (Recommended Approach)

import logging.config

LOGGING_CONFIG = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "standard": {"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"},
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "standard",
            "level": "DEBUG",
        },
        "file": {
            "class": "logging.FileHandler",
            "filename": "app.log",
            "formatter": "standard",
            "level": "WARNING",
            "encoding": "utf-8",
        },
    },
    "root": {"handlers": ["console", "file"], "level": "DEBUG"},
}

logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger(__name__)

zipfile — ZIP Archives

import zipfile

# create a zip archive
with zipfile.ZipFile("archive.zip", "w", zipfile.ZIP_DEFLATED) as zf:
    zf.write("file1.txt")
    zf.write("file2.txt", arcname="renamed.txt")  # rename when storing

# extract
with zipfile.ZipFile("archive.zip", "r") as zf:
    zf.extractall("output_dir/")               # extract all
    zf.extract("renamed.txt", "output_dir/")   # extract a specific file
    print(zf.namelist())                        # list contents

# append a file
with zipfile.ZipFile("archive.zip", "a") as zf:
    zf.write("new_file.txt")

tarfile — TAR Archives

import tarfile

# create a .tar.gz
with tarfile.open("archive.tar.gz", "w:gz") as tf:
    tf.add("src_dir/", arcname="backup")  # archive an entire directory

# extract
with tarfile.open("archive.tar.gz", "r:gz") as tf:
    tf.extractall("output_dir/")
    print(tf.getnames())   # list contents

# format reference:
# "w"     → .tar     (pack only, no compression)
# "w:gz"  → .tar.gz  (gzip compression)
# "w:bz2" → .tar.bz2 (bzip2 compression, usually smaller)
# "w:xz"  → .tar.xz  (xz compression, highest compression ratio)
Last updated on