Claude Code for copyreg: Python Pickle State Registration — Claude Skills 360 Blog
Blog / AI / Claude Code for copyreg: Python Pickle State Registration
AI

Claude Code for copyreg: Python Pickle State Registration

Published: December 4, 2028
Read time: 5 min read
By: Claude Skills 360

Python’s copyreg module lets you register custom reduce functions that control how pickle serializes objects and how copy.copy/copy.deepcopy clone them. import copyreg. Register: copyreg.pickle(type, reduce_fn)reduce_fn(obj) must return a tuple (callable, args) or the extended (callable, args, state, listiter, dictiter) compatible with __reduce_ex__; callable(*args) reconstructs the object. Constructor: copyreg.constructor(fn) — declares fn as a valid reconstruction callable; required before pickle will accept it. Dispatch table: copyreg.dispatch_table — the global dict {type: reduce_fn}; a Pickler instance can also have its own dispatch_table. copyreg._reconstructor(cls, base, state) — the default reconstruction function used internally. Extension codes: copyreg.add_extension(module, name, code) — map a (module, name) global to a short integer code (reduces pickle size for frequent objects); copyreg.remove_extension(module, name, code); copyreg.clear_extension_cache(). Interaction with copy: copyreg registrations affect copy.copy and copy.deepcopy as well as pickle. Claude Code generates version-tolerant serialization schemas, backward-compatible pickle protocols, C-extension type pickle support, and custom deep-copy controls.

CLAUDE.md for copyreg

## copyreg Stack
- Stdlib: import copyreg, pickle, copy
- Register: copyreg.pickle(MyType, reduce_fn)
-            # reduce_fn(obj) -> (callable, args) or (callable, args, state)
- Constructor: copyreg.constructor(reconstruct_fn)
- Table: copyreg.dispatch_table[MyType] = reduce_fn  # same effect
- Note:  affects both pickle AND copy.copy / copy.deepcopy
-        for classes, prefer __reduce__ / __reduce_ex__ / __getstate__

copyreg Pickle State Pipeline

# app/copyregutil.py — reduce fns, versioned schema, C-type, extension codes
from __future__ import annotations

import copyreg
import copy
import io
import pickle
import struct
from dataclasses import dataclass, field, asdict
from typing import Any


# ─────────────────────────────────────────────────────────────────────────────
# 1. Basic reduce registration
# ─────────────────────────────────────────────────────────────────────────────

class Color:
    """
    A simple RGB color type with no __reduce__ — we register via copyreg.
    """
    __slots__ = ("r", "g", "b")

    def __init__(self, r: int, g: int, b: int) -> None:
        self.r = r
        self.g = g
        self.b = b

    def __repr__(self) -> str:
        return f"Color(r={self.r}, g={self.g}, b={self.b})"

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Color):
            return NotImplemented
        return (self.r, self.g, self.b) == (other.r, other.g, other.b)


def _reduce_color(c: Color) -> tuple:
    return (_make_color, (c.r, c.g, c.b))


def _make_color(r: int, g: int, b: int) -> Color:
    return Color(r, g, b)


copyreg.constructor(_make_color)
copyreg.pickle(Color, _reduce_color)


# ─────────────────────────────────────────────────────────────────────────────
# 2. Versioned schema with state migration
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class UserRecord:
    """
    A record that may be pickled from older schema versions.
    Uses copyreg to inject schema_version and migrate on load.
    """
    username: str
    email:    str
    role:     str = "user"      # added in schema v2
    active:   bool = True       # added in schema v3
    _schema_version: int = field(default=3, repr=False, compare=False)


def _reduce_user_record(u: UserRecord) -> tuple:
    state = {
        "username": u.username,
        "email":    u.email,
        "role":     u.role,
        "active":   u.active,
        "_schema_version": u._schema_version,
    }
    return (_reconstruct_user_record, (), state)


def _reconstruct_user_record() -> UserRecord:
    # placeholder constructor; real data applied via __setstate__-like mechanism
    return object.__new__(UserRecord)


def _reconstruct_user_record_full(state: dict) -> UserRecord:
    """Reconstruct with schema migration."""
    v = state.get("_schema_version", 1)
    if v < 2:
        state.setdefault("role", "user")
    if v < 3:
        state.setdefault("active", True)
    state["_schema_version"] = 3
    obj = object.__new__(UserRecord)
    obj.__dict__.update(state)
    return obj


def _reduce_user_record_v2(u: UserRecord) -> tuple:
    """Reduce function that packs state for migration-aware reconstruction."""
    state = {
        "username": u.username,
        "email":    u.email,
        "role":     u.role,
        "active":   u.active,
        "_schema_version": 3,
    }
    return (_reconstruct_user_record_full, (state,))


copyreg.constructor(_reconstruct_user_record_full)
copyreg.pickle(UserRecord, _reduce_user_record_v2)


# ─────────────────────────────────────────────────────────────────────────────
# 3. Registering via dispatch_table for isolated Pickler
# ─────────────────────────────────────────────────────────────────────────────

def make_custom_pickler(
    stream: io.BytesIO,
    extra_reducers: dict[type, "callable"] | None = None,
) -> pickle.Pickler:
    """
    Create a Pickler with a custom dispatch_table that extends copyreg defaults.
    Useful when you need per-Pickler overrides without global registration.

    Example:
        buf = io.BytesIO()
        p = make_custom_pickler(buf, {Color: lambda c: (_make_color, (c.r, c.g, c.b))})
        p.dump(my_obj)
        data = buf.getvalue()
    """
    p = pickle.Pickler(stream)
    # Start from global dispatch_table, then extend
    p.dispatch_table = copyreg.dispatch_table.copy()
    if extra_reducers:
        p.dispatch_table.update(extra_reducers)
    return p


def pickle_with_custom_table(
    obj: object,
    extra_reducers: dict[type, "callable"] | None = None,
) -> bytes:
    """
    Pickle obj using a Pickler with an extended dispatch_table.

    Example:
        data = pickle_with_custom_table(Color(255, 0, 0))
    """
    buf = io.BytesIO()
    p = make_custom_pickler(buf, extra_reducers)
    p.dump(obj)
    return buf.getvalue()


# ─────────────────────────────────────────────────────────────────────────────
# 4. Extension codes (size-optimised pickle)
# ─────────────────────────────────────────────────────────────────────────────

_EXT_MODULE = "app.copyregutil"
_COLOR_EXT_CODE = 1001   # arbitrary unique integer in range [1, 2**31-1]


def register_color_extension() -> None:
    """
    Register Color's constructor as an extension code to shrink pickle size.
    Extension codes replace the full module+qualname string with a short int.

    Example:
        register_color_extension()
        data = pickle.dumps(Color(0, 128, 255))
        print(len(data))   # smaller than without extension
    """
    try:
        copyreg.add_extension(_EXT_MODULE, "_make_color", _COLOR_EXT_CODE)
    except ValueError:
        pass   # already registered


def unregister_color_extension() -> None:
    try:
        copyreg.remove_extension(_EXT_MODULE, "_make_color", _COLOR_EXT_CODE)
    except ValueError:
        pass


# ─────────────────────────────────────────────────────────────────────────────
# 5. Utility: roundtrip + size comparison
# ─────────────────────────────────────────────────────────────────────────────

def roundtrip(obj: object) -> object:
    """Pickle and unpickle obj, returning the reconstructed value."""
    return pickle.loads(pickle.dumps(obj))


def copy_roundtrip(obj: object) -> object:
    """deep-copy obj (uses copyreg dispatch table)."""
    return copy.deepcopy(obj)


def pickle_size(obj: object) -> int:
    """Return the number of bytes in pickle.dumps(obj)."""
    return len(pickle.dumps(obj))


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== copyreg demo ===")

    # ── Color pickle roundtrip ────────────────────────────────────────────────
    print("\n--- Color (copyreg.pickle) ---")
    c = Color(255, 128, 0)
    c2 = roundtrip(c)
    print(f"  original:    {c}")
    print(f"  roundtrip:   {c2}")
    print(f"  equal:       {c == c2}")
    print(f"  pickle size: {pickle_size(c)} bytes")

    # ── Color deepcopy ────────────────────────────────────────────────────────
    print("\n--- copy.deepcopy via copyreg ---")
    c3 = copy_roundtrip(c)
    print(f"  deepcopy:  {c3}  same object: {c3 is c}")

    # ── UserRecord schema migration ──────────────────────────────────────────
    print("\n--- UserRecord versioned schema ---")
    u = UserRecord(username="alice", email="[email protected]")
    u2 = roundtrip(u)
    print(f"  original:  {u}")
    print(f"  roundtrip: {u2}")
    print(f"  equal:     {u == u2}")

    # Simulate loading an old v1 record (missing role, active)
    v1_state = {"username": "bob", "email": "[email protected]", "_schema_version": 1}
    v1_obj = _reconstruct_user_record_full(v1_state)
    print(f"  v1 migrated: {v1_obj}")

    # ── dispatch_table per-Pickler ────────────────────────────────────────────
    print("\n--- custom dispatch_table pickler ---")
    data = pickle_with_custom_table(Color(0, 64, 255))
    c4 = pickle.loads(data)
    print(f"  custom pickler roundtrip: {c4}")

    # ── extension code ────────────────────────────────────────────────────────
    print("\n--- extension code ---")
    before = pickle_size(Color(0, 0, 0))
    register_color_extension()
    after = pickle_size(Color(0, 0, 0))
    print(f"  size without ext code: {before} bytes")
    print(f"  size with ext code:    {after} bytes")
    unregister_color_extension()

    # ── copyreg.dispatch_table contents ──────────────────────────────────────
    print("\n--- dispatch_table entries ---")
    for t in list(copyreg.dispatch_table):
        print(f"  {t.__module__}.{t.__qualname__}")

    print("\n=== done ===")

For the __reduce__ / __reduce_ex__ / __getstate__ / __setstate__ alternative — defining these dunder methods directly on a class is the preferred way to control pickling for classes you own; copyreg is for types you do not own (C extensions, third-party classes) or for globally overriding an existing __reduce__ without modifying the source — use dunder methods for your own classes, copyreg.pickle for foreign types. For the pickle.Pickler.dispatch_table alternative — setting dispatch_table on a Pickler instance (inheriting from copyreg.dispatch_table and overriding specific entries) is the thread-safe, non-global way to customize serialization for a single pickling operation — use Pickler.dispatch_table when you need per-session or per-thread reduce overrides without mutating the global copyreg.dispatch_table that affects all pickling. The Claude Skills 360 bundle includes copyreg skill sets covering _reduce_color/_make_color with copyreg.pickle() basic registration, UserRecord with _reduce_user_record_v2/_reconstruct_user_record_full versioned schema migration, make_custom_pickler()/pickle_with_custom_table() per-Pickler dispatch tables, register_color_extension()/unregister_color_extension() size-optimising extension codes, and roundtrip()/copy_roundtrip()/pickle_size() utilities. Start with the free tier to try pickle registration patterns and copyreg pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free