Serialization and Data Formats
The json module is the standard way to serialize Python objects to JSON and back. It ships with CPython and uses a C extension for performance.
Encoding and Decoding
Section titled “Encoding and Decoding”import json
data = { "host": "db-primary", "port": 5432, "replicas": ["db-replica-1", "db-replica-2"], "config": {"pool_size": 10, "timeout": 30.5}, "read_only": False, "version": None,}
## Serialize to stringjson_str = json.dumps(data, indent=2, sort_keys=True)print(json_str)
## Deserialize from stringparsed = json.loads(json_str)print(parsed["host"]) # db-primaryFile Operations
Section titled “File Operations”import json
# Write to filewith open("config.json", "w") as f: json.dump(data, f, indent=2)
# Read from filewith open("config.json") as f: config = json.load(f)Custom Encoders
Section titled “Custom Encoders”json.dumps accepts a default parameter for objects that are not JSON-serializable by default:
import jsonfrom datetime import datetime, timezonefrom decimal import Decimal
class CustomEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return obj.isoformat() if isinstance(obj, Decimal): return float(obj) if isinstance(obj, set): return list(obj) if isinstance(obj, bytes): return obj.decode("utf-8", errors="replace") return super().default(obj)
data = { "created": datetime(2025, 1, 15, 10, 30, tzinfo=timezone.utc), "price": Decimal("19.99"), "tags": {"python", "json"},}
json_str = json.dumps(data, cls=CustomEncoder, indent=2)print(json_str)Custom Decoders
Section titled “Custom Decoders”import jsonfrom datetime import datetime
def custom_decoder(dct): if "created" in dct and isinstance(dct["created"], str): dct["created"] = datetime.fromisoformat(dct["created"]) return dct
json_str = "{"created": "2025-01-15T10:30:00+00:00", "name": "test"}'data = json.loads(json_str, object_hook=custom_decoder)print(type(data["created"])) # <class 'datetime.datetime'>import jsonimport timeit
data = {"key": "value", "numbers": list(range(1000))}
# dumps/load is faster than dump/load for in-memory operations# C extension is ~10x faster than pure Python fallback
t = timeit.timeit(lambda: json.dumps(data), number=100000)print(f"dumps: {t:.3f}s for 100000 iterations")pickle
Section titled “pickle”pickle serializes Python objects into a binary format. Unlike JSON, it can serialize almost any Python object, including custom classes, functions, and circular references.
Basic Usage
Section titled “Basic Usage”import pickle
class Server: def __init__(self, host, port, credentials): self.host = host self.port = port self.credentials = credentials
def __repr__(self): return f"Server({self.host!r}, {self.port})"
srv = Server("db.example.com", 5432, {"user": "admin", "pass": "secret"})
# Serializedata = pickle.dumps(srv, protocol=pickle.HIGHEST_PROTOCOL)
# Deserializesrv2 = pickle.loads(data)print(srv2) # Server('db.example.com', 5432)print(srv2.credentials) # {'user': "admin'', "pass': "secret''}Protocol Versions
Section titled “Protocol Versions”| Protocol | Python Version | Features |
|---|---|---|
| 0 | All | ASCII, human-readable, backward compatible |
| 1 | 1.4 | Binary format |
| 2 | 2.3 | New-style classes |
| 3 | 3.0 | Bytes objects, no implicit string conversion |
| 4 | 3.4 | Large objects, more types |
| 5 | 3.8 | Out-of-band data, ZSTD compression support |
import pickle
print(pickle.DEFAULT_PROTOCOL) # 5 (on Python 3.12)print(pickle.HIGHEST_PROTOCOL) # 5
# Use highest protocol for best performance and compatibilitydata = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)The standard pickle cannot serialize lambda functions, dynamically defined classes, or objects Defined in __main__. cloudpickle extends pickle to handle these cases:
import cloudpickle
# Standard pickle cannot handle this:func = lambda x: x ** 2# pickle.dumps(func) # AttributeError: Can"t pickle local object
# cloudpickle handles it:data = cloudpickle.dumps(func)func2 = cloudpickle.loads(data)print(func2(5)) # 25cloudpickle is used by distributed computing frameworks like PySpark, Dask, and Ray to ship Closures across processes.
Pickle and __reduce__
Section titled “Pickle and __reduce__”The __reduce__ method controls how an object is pickled:
import pickle
class Config: def __init__(self, path): self.path = path self._data = self._load(path)
def _load(self, path): return {"loaded_from": path}
def __reduce__(self): # Return (callable, args) — pickle will call Config(*args) return (Config, (self.path,))
c = Config("/etc/app/config.yaml")data = pickle.dumps(c)c2 = pickle.loads(data)print(c2._data) # {'loaded_from': "/etc/app/config.yaml''}YAML is a human-readable data serialization format. Python uses the third-party PyYAML library.
Basic Usage
Section titled “Basic Usage”import yaml
config = { "database": { "host": "localhost", "port": 5432, "name": "production", }, "logging": { "level": "INFO", "handlers": ["stdout", "file"], },}
# Serializeyaml_str = yaml.dump(config, default_flow_style=False, sort_keys=False)print(yaml_str)
# Deserializedata = yaml.safe_load(yaml_str)print(data["database"]["host"]) # localhostsafe_load vs load
Section titled “safe_load vs load”import yaml
# Define a custom constructor that only creates known typesdef env_variable_constructor(loader, node): value = loader.construct_scalar(node) import os return os.getenv(value, value)
# Register with a custom SafeLoaderclass CustomSafeLoader(yaml.SafeLoader): pass
CustomSafeLoader.add_constructor("!env", env_variable_constructor)
yaml_str = """host: !env HOST_NAMEport: !env PORT_NUMBER"""data = yaml.load(yaml_str, Loader=CustomSafeLoader)print(data) # {'host': "localhost'', "port': "localhost''} (or env values)TOML (Tom”s Obvious Minimal Language) is designed for configuration files. Python 3.11+ includes tomllib in the standard library.
Reading TOML (Python 3.11+)
Section titled “Reading TOML (Python 3.11+)”import tomllib # Python 3.11+
with open("pyproject.toml", "rb") as f: config = tomllib.load(f)
print(config["project"]["name"])print(config["tool"]["pytest"]["testpaths"])Reading TOML (Python 3.10 and earlier)
Section titled “Reading TOML (Python 3.10 and earlier)”# pip install tomliimport tomli
with open("pyproject.toml", "rb") as f: config = tomli.load(f)Writing TOML
Section titled “Writing TOML”Python’s built-in tomllib is read-only. For writing, use tomli_w:
# pip install tomli_wimport tomli_w
config = { "database": { "host": "localhost", "port": 5432, }, "logging": { "level": "INFO", },}
with open("config.toml", "wb") as f: tomli_w.dump(config, f)TOML Syntax
Section titled “TOML Syntax”[server]host = "0.0.0.0"port = 8080debug = false
[server.cors]allowed_origins = ["https://example.com", "https://app.example.com"]max_age = 3600
[database]host = "localhost"port = 5432name = "myapp"
[[users]]name = "admin"role = "superuser"
[[users]]name = "viewer"role = "readonly"The csv module handles reading and writing CSV files.
Reading CSV
Section titled “Reading CSV”import csv
# Basic readingwith open("data.csv", newline="") as f: reader = csv.reader(f) headers = next(reader) for row in reader: print(dict(zip(headers, row)))
# DictReader — recommended for most use caseswith open("data.csv", newline="") as f: reader = csv.DictReader(f) for row in reader: print(row["name"], row["email"])Writing CSV
Section titled “Writing CSV”import csv
headers = ["name", "email", "role"]rows = [ {"name": "Alice", "email": "alice@example.com", "role": "admin"}, {"name": "Bob", "email": "bob@example.com", "role": "user"},]
with open("output.csv", "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=headers) writer.writeheader() writer.writerows(rows)Quoting and Special Characters
Section titled “Quoting and Special Characters”import csv
# Control quoting behaviorwith open("quoted.csv", "w", newline="") as f: writer = csv.writer(f, quoting=csv.QUOTE_ALL) writer.writerow(["name", "description"]) writer.writerow(['Alice', 'Has a "title" and, commas'])
# csv.QUOTE_MINIMAL (default) — quote only when necessary# csv.QUOTE_ALL — quote everything# csv.QUOTE_NONNUMERIC — quote non-numeric values# csv.QUOTE_NONE — never quote (raises error if quoting needed)- Essential Modules: Covers the core Python modules that provide foundational functionality, including data structures used in serialization.
- File I/O: Shows how to read and write files, which is essential for saving and loading serialized data.
- CLI Tools: Demonstrates how to use command-line interfaces to process and transform serialized data.
- Data Validation: Explains how to validate serialized data using Pydantic models and other validation techniques.