or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdcore-serialization.mddiagnostic-tools.mdindex.mdpickler-classes.mdsession-management.mdsource-analysis.mdtemp-operations.mdtype-registry.md

type-registry.mddocs/

0

# Type Registry System

1

2

dill provides a flexible type registry system that allows registration of custom types and extension of serialization capabilities to handle new object types that are not supported by default.

3

4

## Core Registry Functions

5

6

### Type Registration

7

8

```python { .api }

9

def register(t):

10

"""

11

Register a type with the pickler.

12

13

Decorator function that registers a custom pickling function for a specific

14

type, allowing dill to handle objects of that type during serialization.

15

16

Parameters:

17

- t: type, the type to register for custom pickling

18

19

Returns:

20

function: decorator function that takes the pickling function

21

22

Usage:

23

@dill.register(MyCustomType)

24

def save_custom_type(pickler, obj):

25

# Custom pickling logic

26

pass

27

"""

28

29

def pickle(t, func):

30

"""

31

Add a type to the pickle dispatch table.

32

33

Directly associates a type with a pickling function in the dispatch table,

34

enabling automatic handling of objects of that type.

35

36

Parameters:

37

- t: type, the type to add to dispatch table

38

- func: function, pickling function that handles objects of type t

39

40

Returns:

41

None

42

43

Raises:

44

- TypeError: when type or function parameters are invalid

45

"""

46

47

def extend(use_dill=True):

48

"""

49

Add or remove dill types to/from the pickle registry.

50

51

Controls whether dill's extended types are available in the standard

52

pickle module's dispatch table, allowing integration with existing code.

53

54

Parameters:

55

- use_dill: bool, if True extend dispatch table with dill types,

56

if False revert to standard pickle types only

57

58

Returns:

59

None

60

"""

61

```

62

63

### Registry Inspection

64

65

```python { .api }

66

def dispatch_table():

67

"""

68

Get the current dispatch table.

69

70

Returns the current pickle dispatch table showing all registered

71

types and their associated pickling functions.

72

73

Returns:

74

dict: mapping of types to pickling functions

75

"""

76

```

77

78

## Usage Examples

79

80

### Basic Type Registration

81

82

```python

83

import dill

84

85

# Define a custom class

86

class CustomData:

87

def __init__(self, data, metadata=None):

88

self.data = data

89

self.metadata = metadata or {}

90

91

def __repr__(self):

92

return f"CustomData({self.data}, {self.metadata})"

93

94

# Register custom pickling function using decorator

95

@dill.register(CustomData)

96

def save_custom_data(pickler, obj):

97

"""Custom pickling function for CustomData objects."""

98

# Save the class reference

99

pickler.save_reduce(_restore_custom_data,

100

(obj.data, obj.metadata), obj=obj)

101

102

def _restore_custom_data(data, metadata):

103

"""Helper function to restore CustomData objects."""

104

return CustomData(data, metadata)

105

106

# Test the registration

107

custom_obj = CustomData([1, 2, 3], {"version": "1.0"})

108

109

# Now it can be pickled

110

serialized = dill.dumps(custom_obj)

111

restored = dill.loads(serialized)

112

113

print(f"Original: {custom_obj}")

114

print(f"Restored: {restored}")

115

```

116

117

### Direct Dispatch Table Modification

118

119

```python

120

import dill

121

122

class SpecialContainer:

123

def __init__(self, items):

124

self.items = list(items)

125

126

def __eq__(self, other):

127

return isinstance(other, SpecialContainer) and self.items == other.items

128

129

def pickle_special_container(pickler, obj):

130

"""Pickling function for SpecialContainer."""

131

# Use pickle protocol to save class and items

132

pickler.save_reduce(SpecialContainer, (obj.items,), obj=obj)

133

134

# Add to dispatch table directly

135

dill.pickle(SpecialContainer, pickle_special_container)

136

137

# Test the registration

138

container = SpecialContainer([1, 2, 3, 4])

139

pickled_data = dill.dumps(container)

140

unpickled_container = dill.loads(pickled_data)

141

142

print(f"Original == Restored: {container == unpickled_container}")

143

```

144

145

### Complex Type Registration

146

147

```python

148

import dill

149

import weakref

150

151

class Node:

152

"""Tree node with parent/child relationships."""

153

def __init__(self, value, parent=None):

154

self.value = value

155

self.children = []

156

self._parent_ref = None

157

if parent:

158

self.set_parent(parent)

159

160

def set_parent(self, parent):

161

if self._parent_ref:

162

old_parent = self._parent_ref()

163

if old_parent:

164

old_parent.children.remove(self)

165

166

self._parent_ref = weakref.ref(parent) if parent else None

167

if parent:

168

parent.children.append(self)

169

170

@property

171

def parent(self):

172

return self._parent_ref() if self._parent_ref else None

173

174

# Register complex type with custom handling

175

@dill.register(Node)

176

def save_node(pickler, obj):

177

"""Custom pickling for Node objects."""

178

# Save value and children (parent will be reconstructed)

179

pickler.save_reduce(_restore_node,

180

(obj.value, obj.children), obj=obj)

181

182

def _restore_node(value, children):

183

"""Restore Node with proper parent/child relationships."""

184

node = Node(value)

185

186

# Restore children and set parent relationships

187

for child in children:

188

if isinstance(child, Node):

189

child.set_parent(node)

190

else:

191

# Handle case where child was also pickled/unpickled

192

node.children.append(child)

193

194

return node

195

196

# Test complex type registration

197

root = Node("root")

198

child1 = Node("child1", root)

199

child2 = Node("child2", root)

200

grandchild = Node("grandchild", child1)

201

202

# Pickle the entire tree

203

tree_data = dill.dumps(root)

204

restored_root = dill.loads(tree_data)

205

206

print(f"Root children: {[child.value for child in restored_root.children]}")

207

print(f"Child1 parent: {restored_root.children[0].parent.value}")

208

```

209

210

### Registry Management

211

212

```python

213

import dill

214

215

class RegistryManager:

216

"""Manage custom type registrations."""

217

218

def __init__(self):

219

self.registered_types = {}

220

self.original_dispatch = None

221

222

def register_type(self, type_class, save_func, restore_func=None):

223

"""Register a type with save and optional restore functions."""

224

if restore_func:

225

# Store restore function for later use

226

self.registered_types[type_class] = {

227

'save': save_func,

228

'restore': restore_func

229

}

230

231

# Create wrapper that includes restore function

232

def save_wrapper(pickler, obj):

233

pickler.save_reduce(restore_func, save_func(obj), obj=obj)

234

235

dill.register(type_class)(save_wrapper)

236

else:

237

# Direct registration

238

dill.register(type_class)(save_func)

239

self.registered_types[type_class] = {'save': save_func}

240

241

def unregister_type(self, type_class):

242

"""Remove type from registry."""

243

if type_class in self.registered_types:

244

# Remove from dill's dispatch table

245

dispatch = dill.dispatch_table()

246

if type_class in dispatch:

247

del dispatch[type_class]

248

249

del self.registered_types[type_class]

250

251

def list_registered_types(self):

252

"""List all registered custom types."""

253

return list(self.registered_types.keys())

254

255

def backup_dispatch_table(self):

256

"""Backup current dispatch table."""

257

self.original_dispatch = dill.dispatch_table().copy()

258

259

def restore_dispatch_table(self):

260

\"\"\"Restore original dispatch table.\"\"\"

261

if self.original_dispatch:

262

current_dispatch = dill.dispatch_table()

263

current_dispatch.clear()

264

current_dispatch.update(self.original_dispatch)

265

266

# Usage example

267

registry = RegistryManager()

268

269

# Backup original state

270

registry.backup_dispatch_table()

271

272

# Register multiple types

273

registry.register_type(

274

CustomData,

275

lambda obj: (obj.data, obj.metadata),

276

lambda data, meta: CustomData(data, meta)

277

)

278

279

registry.register_type(

280

SpecialContainer,

281

lambda obj: (obj.items,),

282

lambda items: SpecialContainer(items)

283

)

284

285

print(f"Registered types: {[t.__name__ for t in registry.list_registered_types()]}")

286

287

# Test registrations

288

test_objects = [

289

CustomData([1, 2, 3], {"test": True}),

290

SpecialContainer(["a", "b", "c"])

291

]

292

293

for obj in test_objects:

294

try:

295

data = dill.dumps(obj)

296

restored = dill.loads(data)

297

print(f"✓ {type(obj).__name__}: Successfully pickled and restored")

298

except Exception as e:

299

print(f"✗ {type(obj).__name__}: Failed - {e}")

300

301

# Clean up

302

registry.restore_dispatch_table()

303

```

304

305

## Advanced Registry Techniques

306

307

### Conditional Type Registration

308

309

```python

310

import dill

311

import sys

312

313

class ConditionalRegistry:

314

"""Register types based on conditions."""

315

316

@staticmethod

317

def register_if_available(type_name, module_name, save_func, restore_func=None):

318

"""Register type only if module is available."""

319

try:

320

module = __import__(module_name)

321

type_class = getattr(module, type_name)

322

323

if restore_func:

324

def save_wrapper(pickler, obj):

325

pickler.save_reduce(restore_func, save_func(obj), obj=obj)

326

dill.register(type_class)(save_wrapper)

327

else:

328

dill.register(type_class)(save_func)

329

330

print(f"✓ Registered {type_name} from {module_name}")

331

return True

332

333

except (ImportError, AttributeError) as e:

334

print(f"✗ Could not register {type_name}: {e}")

335

return False

336

337

@staticmethod

338

def register_for_python_version(min_version, type_class, save_func):

339

"""Register type only for specific Python versions."""

340

if sys.version_info >= min_version:

341

dill.register(type_class)(save_func)

342

print(f"✓ Registered {type_class.__name__} for Python {sys.version_info[:2]}")

343

return True

344

else:

345

print(f"✗ Skipped {type_class.__name__} - requires Python {min_version}")

346

return False

347

348

# Example usage

349

registry = ConditionalRegistry()

350

351

# Register numpy array if numpy is available

352

registry.register_if_available(

353

'ndarray', 'numpy',

354

lambda obj: (obj.tobytes(), obj.dtype, obj.shape),

355

lambda data, dtype, shape: __import__('numpy').frombuffer(data, dtype).reshape(shape)

356

)

357

358

# Register pathlib.Path for Python 3.4+

359

import pathlib

360

registry.register_for_python_version(

361

(3, 4),

362

pathlib.Path,

363

lambda obj: str(obj)

364

)

365

```

366

367

### Dynamic Type Discovery

368

369

```python

370

import dill

371

import inspect

372

373

class AutoRegistry:

374

"""Automatically discover and register types."""

375

376

def __init__(self):

377

self.auto_registered = set()

378

379

def auto_register_module(self, module, save_pattern=None, exclude=None):

380

"""Automatically register types from a module."""

381

exclude = exclude or []

382

registered_count = 0

383

384

for name, obj in inspect.getmembers(module):

385

# Skip private and excluded items

386

if name.startswith('_') or name in exclude:

387

continue

388

389

# Only register classes

390

if inspect.isclass(obj):

391

# Check if we can create a default save function

392

if self._can_auto_register(obj):

393

self._auto_register_type(obj)

394

registered_count += 1

395

396

print(f\"Auto-registered {registered_count} types from {module.__name__}\")\n return registered_count\n \n def _can_auto_register(self, type_class):\n \"\"\"Check if type can be auto-registered.\"\"\"\n try:\n # Check if type has __dict__ or __slots__\n if hasattr(type_class, '__dict__') or hasattr(type_class, '__slots__'):\n return True\n \n # Check if it's a simple dataclass-like structure\n if hasattr(type_class, '__init__'):\n sig = inspect.signature(type_class.__init__)\n # Simple heuristic: if all parameters have defaults or annotations\n return len(sig.parameters) <= 5 # Reasonable limit\n \n except Exception:\n pass\n \n return False\n \n def _auto_register_type(self, type_class):\n \"\"\"Automatically register a type with default behavior.\"\"\"\n if type_class in self.auto_registered:\n return\n \n def auto_save_func(pickler, obj):\n # Generic save function using __dict__ or __getstate__\n if hasattr(obj, '__getstate__'):\n state = obj.__getstate__()\n pickler.save_reduce(_auto_restore_with_state, \n (type_class, state), obj=obj)\n elif hasattr(obj, '__dict__'):\n pickler.save_reduce(_auto_restore_with_dict,\n (type_class, obj.__dict__), obj=obj)\n else:\n # Fallback to trying __reduce__\n pickler.save_reduce(type_class, (), obj=obj)\n \n dill.register(type_class)(auto_save_func)\n self.auto_registered.add(type_class)\n print(f\"Auto-registered {type_class.__name__}\")\n\ndef _auto_restore_with_state(type_class, state):\n \"\"\"Restore object using __setstate__.\"\"\"\n obj = type_class.__new__(type_class)\n if hasattr(obj, '__setstate__'):\n obj.__setstate__(state)\n else:\n obj.__dict__.update(state)\n return obj\n\ndef _auto_restore_with_dict(type_class, obj_dict):\n \"\"\"Restore object using __dict__.\"\"\"\n obj = type_class.__new__(type_class)\n obj.__dict__.update(obj_dict)\n return obj\n\n# Example usage\nauto_registry = AutoRegistry()\n\n# Auto-register types from a custom module\n# auto_registry.auto_register_module(my_custom_module, exclude=['BaseClass'])\n```\n\n## Integration with Existing Code\n\n### Backward Compatibility\n\n```python\nimport dill\nimport pickle\n\nclass CompatibilityManager:\n \"\"\"Manage compatibility between dill and standard pickle.\"\"\"\n \n def __init__(self):\n self.dill_enabled = True\n \n def enable_dill_types(self):\n \"\"\"Enable dill types in pickle dispatch table.\"\"\"\n dill.extend(use_dill=True)\n self.dill_enabled = True\n print(\"Dill types enabled in pickle\")\n \n def disable_dill_types(self):\n \"\"\"Disable dill types, use only standard pickle.\"\"\"\n dill.extend(use_dill=False)\n self.dill_enabled = False\n print(\"Dill types disabled, using standard pickle only\")\n \n def test_compatibility(self, test_objects):\n \"\"\"Test objects with both pickle and dill.\"\"\"\n results = {}\n \n for name, obj in test_objects.items():\n results[name] = {\n 'dill': self._test_with_dill(obj),\n 'pickle': self._test_with_pickle(obj)\n }\n \n return results\n \n def _test_with_dill(self, obj):\n try:\n data = dill.dumps(obj)\n dill.loads(data)\n return \"✓ Success\"\n except Exception as e:\n return f\"✗ Failed: {e}\"\n \n def _test_with_pickle(self, obj):\n try:\n data = pickle.dumps(obj)\n pickle.loads(data)\n return \"✓ Success\"\n except Exception as e:\n return f\"✗ Failed: {e}\"\n\n# Usage\ncompat = CompatibilityManager()\n\n# Test various objects\ntest_objects = {\n 'function': lambda x: x + 1,\n 'class': CustomData,\n 'instance': CustomData([1, 2, 3]),\n 'list': [1, 2, 3, 4, 5]\n}\n\nprint(\"Testing with dill types enabled:\")\ncompat.enable_dill_types()\nresults_enabled = compat.test_compatibility(test_objects)\n\nprint(\"\\nTesting with dill types disabled:\")\ncompat.disable_dill_types()\nresults_disabled = compat.test_compatibility(test_objects)\n\n# Compare results\nprint(\"\\nCompatibility Results:\")\nprint(\"-\" * 50)\nfor name in test_objects:\n print(f\"{name}:\")\n print(f\" Dill enabled - pickle: {results_enabled[name]['pickle']}\")\n print(f\" Dill disabled - pickle: {results_disabled[name]['pickle']}\")\n print(f\" Dill enabled - dill: {results_enabled[name]['dill']}\")\n\n# Re-enable for normal operation\ncompat.enable_dill_types()\n```\n\n## Best Practices\n\n### Registry Guidelines\n\n1. **Performance**: Keep pickling functions simple and fast\n2. **Robustness**: Handle edge cases and provide error recovery\n3. **Compatibility**: Test with different Python versions and environments\n4. **Documentation**: Document custom types and their pickling behavior\n5. **Testing**: Verify that pickled objects restore correctly with full functionality\n\n### Error Handling\n\n```python\nimport dill\nimport logging\n\nclass RobustRegistry:\n \"\"\"Registry with comprehensive error handling.\"\"\"\n \n def __init__(self):\n self.logger = logging.getLogger(__name__)\n \n def safe_register(self, type_class, save_func, restore_func=None):\n \"\"\"Register type with error handling.\"\"\"\n try:\n # Validate inputs\n if not isinstance(type_class, type):\n raise TypeError(\"type_class must be a type\")\n \n if not callable(save_func):\n raise TypeError(\"save_func must be callable\")\n \n if restore_func and not callable(restore_func):\n raise TypeError(\"restore_func must be callable\")\n \n # Test the registration with a dummy object\n self._test_registration(type_class, save_func, restore_func)\n \n # Perform actual registration\n if restore_func:\n def wrapper(pickler, obj):\n try:\n args = save_func(obj)\n pickler.save_reduce(restore_func, args, obj=obj)\n except Exception as e:\n self.logger.error(f\"Error saving {type_class.__name__}: {e}\")\n raise\n \n dill.register(type_class)(wrapper)\n else:\n dill.register(type_class)(save_func)\n \n self.logger.info(f\"Successfully registered {type_class.__name__}\")\n return True\n \n except Exception as e:\n self.logger.error(f\"Failed to register {type_class.__name__}: {e}\")\n return False\n \n def _test_registration(self, type_class, save_func, restore_func):\n \"\"\"Test registration with dummy data.\"\"\"\n # This would need to be implemented based on specific type requirements\n pass\n\n# Usage\nrobust_registry = RobustRegistry()\nlogging.basicConfig(level=logging.INFO)\n\nsuccess = robust_registry.safe_register(\n CustomData,\n lambda obj: (obj.data, obj.metadata),\n lambda data, meta: CustomData(data, meta)\n)\n\nif success:\n print(\"Registration successful\")\nelse:\n print(\"Registration failed - check logs\")\n```