0
# Diagnostic Tools
1
2
dill provides comprehensive diagnostic tools for analyzing serialization capabilities, identifying problematic objects, and debugging pickling issues with detailed error reporting and analysis.
3
4
## Core Diagnostic Functions
5
6
### Pickling Capability Testing
7
8
```python { .api }
9
def pickles(obj, exact=False, safe=False, **kwds):
10
"""
11
Check if an object can be pickled.
12
13
Tests whether an object can be successfully serialized and deserialized
14
using dill, providing a simple boolean result for compatibility checking.
15
16
Parameters:
17
- obj: object to test for pickling capability
18
- exact: bool, use exact type matching for compatibility testing
19
- safe: bool, use safe mode to avoid side effects during testing
20
- **kwds: additional keyword arguments passed to dumps/loads
21
22
Returns:
23
bool: True if object can be pickled and unpickled successfully
24
25
Raises:
26
- Exception: only when safe=False and testing encounters severe errors
27
"""
28
29
def check(obj, *args, **kwds):
30
"""
31
Check for pickling errors and print diagnostic information.
32
33
Performs comprehensive pickling analysis and displays detailed
34
information about any issues encountered during serialization testing.
35
36
Parameters:
37
- obj: object to check for pickling errors
38
- *args: positional arguments passed to pickles()
39
- **kwds: keyword arguments passed to pickles()
40
41
Returns:
42
bool: True if no errors found, False if pickling issues detected
43
"""
44
```
45
46
### Problem Object Detection
47
48
```python { .api }
49
def baditems(obj, exact=False, safe=False):
50
"""
51
Find objects that cannot be pickled within a complex structure.
52
53
Recursively analyzes an object to identify specific items that
54
prevent successful serialization, helping isolate problematic components.
55
56
Parameters:
57
- obj: object to analyze for unpickleable items
58
- exact: bool, use exact type matching for analysis
59
- safe: bool, use safe mode to avoid side effects
60
61
Returns:
62
list: list of unpickleable objects found in the structure
63
"""
64
65
def badobjects(obj, depth=0, exact=False, safe=False):
66
"""
67
Get objects that fail to pickle.
68
69
Analyzes object structure at specific depth levels to identify
70
problematic objects that prevent serialization at different nesting levels.
71
72
Parameters:
73
- obj: object to analyze
74
- depth: int, analysis depth (0 for immediate object only, >0 for recursive analysis)
75
- exact: bool, use exact type matching
76
- safe: bool, use safe mode to avoid side effects
77
78
Returns:
79
object or dict: at depth=0 returns the object if it fails to pickle (None if it pickles),
80
at depth>0 returns dict mapping attribute names to bad objects
81
"""
82
83
def badtypes(obj, depth=0, exact=False, safe=False):
84
"""
85
Get types for objects that fail to pickle.
86
87
Identifies specific types that cause pickling failures, providing
88
type-level analysis of serialization compatibility issues.
89
90
Parameters:
91
- obj: object to analyze for problematic types
92
- depth: int, analysis depth (0 for immediate object only, >0 for recursive analysis)
93
- exact: bool, use exact type matching
94
- safe: bool, use safe mode
95
96
Returns:
97
type or dict: at depth=0 returns the type if object fails to pickle (None if it pickles),
98
at depth>0 returns dict mapping attribute names to problematic types
99
"""
100
101
def errors(obj, depth=0, exact=False, safe=False):
102
"""
103
Get detailed pickling error information.
104
105
Provides comprehensive error analysis including specific error messages,
106
problematic objects, and suggested solutions for pickling failures.
107
108
Parameters:
109
- obj: object to analyze for errors
110
- depth: int, maximum analysis depth (0 for unlimited)
111
- exact: bool, use exact type matching
112
- safe: bool, use safe mode to avoid side effects
113
114
Returns:
115
list: list of detailed error descriptions and diagnostic information
116
"""
117
```
118
119
## Usage Examples
120
121
### Basic Compatibility Testing
122
123
```python
124
import dill
125
126
# Test various object types
127
def test_function():
128
return "Hello from function"
129
130
class TestClass:
131
def __init__(self, value):
132
self.value = value
133
134
def method(self):
135
return self.value * 2
136
137
# Test individual objects
138
print(dill.pickles(test_function)) # True
139
print(dill.pickles(TestClass)) # True
140
print(dill.pickles(TestClass(42))) # True
141
142
# Test problematic objects
143
import threading
144
lock = threading.Lock()
145
print(dill.pickles(lock)) # False (locks can't be pickled)
146
147
# Use safe mode for testing
148
print(dill.pickles(dangerous_object, safe=True))
149
```
150
151
### Comprehensive Error Analysis
152
153
```python
154
import dill
155
156
# Complex object with potential issues
157
class ComplexObject:
158
def __init__(self):
159
self.data = [1, 2, 3]
160
self.function = lambda x: x + 1
161
self.lock = threading.Lock() # Problematic
162
self.nested = {
163
'good': 'string',
164
'bad': open('/dev/null', 'r') # File handle - problematic
165
}
166
167
complex_obj = ComplexObject()
168
169
# Quick check
170
if not dill.check(complex_obj):
171
print("Object has pickling issues")
172
173
# Find problematic items
174
bad_items = dill.baditems(complex_obj)
175
print(f"Found {len(bad_items)} unpickleable items:")
176
for item in bad_items:
177
print(f" {type(item)}: {item}")
178
179
# Get detailed error information
180
error_details = dill.errors(complex_obj)
181
print("\nDetailed errors:")
182
for error in error_details:
183
print(f" {error}")
184
```
185
186
### Automated Testing Suite
187
188
```python
189
import dill
190
191
def comprehensive_pickle_test(obj, name="object"):
192
"""Comprehensive pickling analysis for an object."""
193
print(f"\nTesting {name}...")
194
print("=" * 50)
195
196
# Basic pickling test
197
can_pickle = dill.pickles(obj, safe=True)
198
print(f"Can pickle: {can_pickle}")
199
200
if not can_pickle:
201
# Detailed analysis
202
print("\nProblematic items:")
203
bad_items = dill.baditems(obj, safe=True)
204
for i, item in enumerate(bad_items):
205
print(f" {i+1}. {type(item).__name__}: {repr(item)[:50]}...")
206
207
print("\nProblematic types:")
208
bad_types = dill.badtypes(obj, safe=True)
209
for i, bad_type in enumerate(bad_types):
210
print(f" {i+1}. {bad_type}")
211
212
print("\nError details:")
213
error_list = dill.errors(obj, safe=True)
214
for i, error in enumerate(error_list):
215
print(f" {i+1}. {error}")
216
217
return can_pickle
218
219
# Test various objects
220
test_objects = {
221
'simple_function': lambda x: x + 1,
222
'complex_class': ComplexObject(),
223
'nested_structure': {'functions': [lambda x: x, lambda y: y**2]},
224
'problematic_object': complex_obj
225
}
226
227
results = {}
228
for name, obj in test_objects.items():
229
results[name] = comprehensive_pickle_test(obj, name)
230
231
print(f"\nSummary: {sum(results.values())}/{len(results)} objects can be pickled")
232
```
233
234
### Interactive Debugging Session
235
236
```python
237
import dill
238
239
def debug_pickling_issues(obj):
240
"""Interactive debugging for pickling issues."""
241
print("Starting pickling diagnosis...")
242
243
# Step 1: Basic test
244
if dill.pickles(obj):
245
print("✓ Object can be pickled successfully")
246
return True
247
248
print("✗ Object cannot be pickled")
249
250
# Step 2: Identify bad items
251
print("\nSearching for problematic items...")
252
bad_items = dill.baditems(obj)
253
254
if not bad_items:
255
print("No specific bad items found - issue may be with object structure")
256
return False
257
258
print(f"Found {len(bad_items)} problematic items:")
259
260
# Step 3: Categorize issues
261
by_type = {}
262
for item in bad_items:
263
item_type = type(item).__name__
264
if item_type not in by_type:
265
by_type[item_type] = []
266
by_type[item_type].append(item)
267
268
# Step 4: Provide solutions
269
for item_type, items in by_type.items():
270
print(f"\n{item_type} objects ({len(items)} found):")
271
for i, item in enumerate(items[:3]): # Show first 3
272
print(f" {i+1}. {repr(item)[:60]}...")
273
274
# Suggest solutions
275
if item_type == 'Lock':
276
print(" → Solution: Remove locks or replace with serializable alternatives")
277
elif item_type == 'TextIOWrapper':
278
print(" → Solution: Close files or use file paths instead of handles")
279
elif item_type == 'module':
280
print(" → Solution: Import modules by name rather than storing references")
281
else:
282
print(f" → Solution: Remove {item_type} objects or find serializable alternatives")
283
284
return False
285
286
# Usage
287
debug_pickling_issues(problematic_object)
288
```
289
290
## Advanced Diagnostic Techniques
291
292
### Depth-Based Analysis
293
294
```python
295
import dill
296
297
def analyze_by_depth(obj, max_depth=5):
298
"""Analyze object pickling issues by depth level."""
299
print("Depth-based analysis:")
300
print("-" * 30)
301
302
for depth in range(max_depth + 1):
303
bad_objects = dill.badobjects(obj, depth=depth, safe=True)
304
bad_types = dill.badtypes(obj, depth=depth, safe=True)
305
306
print(f"Depth {depth}:")
307
print(f" Bad objects: {len(bad_objects)}")
308
print(f" Bad types: {len(set(t.__name__ for t in bad_types))}")
309
310
if bad_objects:
311
# Show sample bad objects at this depth
312
sample = bad_objects[:2]
313
for obj in sample:
314
print(f" {type(obj).__name__}: {repr(obj)[:40]}...")
315
316
# Usage
317
analyze_by_depth(complex_nested_object)
318
```
319
320
### Custom Diagnostic Rules
321
322
```python
323
import dill
324
325
class PicklingDiagnostic:
326
"""Custom diagnostic tool for pickling analysis."""
327
328
def __init__(self):
329
self.rules = {
330
'file_handles': lambda obj: hasattr(obj, 'read') and hasattr(obj, 'write'),
331
'locks': lambda obj: hasattr(obj, 'acquire') and hasattr(obj, 'release'),
332
'generators': lambda obj: hasattr(obj, '__next__') and hasattr(obj, '__iter__'),
333
'lambda_functions': lambda obj: callable(obj) and obj.__name__ == '<lambda>',
334
}
335
336
def diagnose(self, obj):
337
"""Run custom diagnostic rules."""
338
issues = {}
339
340
# Test overall pickling
341
can_pickle = dill.pickles(obj, safe=True)
342
issues['can_pickle'] = can_pickle
343
344
if can_pickle:
345
return issues
346
347
# Run custom rules
348
bad_items = dill.baditems(obj, safe=True)
349
350
for rule_name, rule_func in self.rules.items():
351
matching_items = [item for item in bad_items if rule_func(item)]
352
if matching_items:
353
issues[rule_name] = matching_items
354
355
return issues
356
357
def report(self, obj, name="object"):
358
"""Generate diagnostic report."""
359
issues = self.diagnose(obj)
360
361
print(f"Diagnostic Report for {name}")
362
print("=" * 40)
363
364
if issues.get('can_pickle', False):
365
print("✓ Object can be pickled successfully")
366
return
367
368
print("✗ Object cannot be pickled")
369
print("\nIssues found:")
370
371
for issue_type, items in issues.items():
372
if issue_type == 'can_pickle':
373
continue
374
375
print(f"\n{issue_type.replace('_', ' ').title()}:")
376
for item in items[:3]: # Show first 3
377
print(f" - {type(item).__name__}: {repr(item)[:50]}...")
378
379
# Usage
380
diagnostic = PicklingDiagnostic()
381
diagnostic.report(complex_object, "MyComplexObject")
382
```
383
384
### Performance Impact Analysis
385
386
```python
387
import dill
388
import time
389
390
def performance_diagnostic(obj, name="object"):
391
"""Analyze performance impact of pickling."""
392
print(f"Performance analysis for {name}")
393
print("-" * 40)
394
395
# Test if object can be pickled
396
if not dill.pickles(obj, safe=True):
397
print("Object cannot be pickled - skipping performance test")
398
return
399
400
# Measure serialization time
401
start_time = time.time()
402
serialized = dill.dumps(obj)
403
serialize_time = time.time() - start_time
404
405
# Measure deserialization time
406
start_time = time.time()
407
dill.loads(serialized)
408
deserialize_time = time.time() - start_time
409
410
# Report results
411
print(f"Serialization time: {serialize_time:.4f} seconds")
412
print(f"Deserialization time: {deserialize_time:.4f} seconds")
413
print(f"Serialized size: {len(serialized):,} bytes")
414
print(f"Compression ratio: {len(str(obj).encode()) / len(serialized):.2f}x")
415
416
# Usage with different objects
417
performance_diagnostic(large_data_structure, "Large Data Structure")
418
performance_diagnostic(complex_function, "Complex Function")
419
performance_diagnostic(class_instance, "Class Instance")
420
```
421
422
## Integration with Development Workflow
423
424
### Pre-commit Pickling Validation
425
426
```python
427
import dill
428
import sys
429
430
def validate_pickling(modules_to_test):
431
"""Validate that key objects can be pickled before commit."""
432
print("Running pickling validation...")
433
434
failed_objects = []
435
436
for module_name in modules_to_test:
437
try:
438
module = __import__(module_name)
439
440
# Test key objects in module
441
for attr_name in dir(module):
442
if not attr_name.startswith('_'):
443
obj = getattr(module, attr_name)
444
445
if callable(obj) or hasattr(obj, '__dict__'):
446
if not dill.pickles(obj, safe=True):
447
failed_objects.append(f"{module_name}.{attr_name}")
448
449
except ImportError:
450
print(f"Could not import {module_name}")
451
452
if failed_objects:
453
print("❌ Pickling validation failed for:")
454
for obj in failed_objects:
455
print(f" - {obj}")
456
return False
457
else:
458
print("✓ All objects pass pickling validation")
459
return True
460
461
# Usage in CI/CD pipeline
462
if __name__ == "__main__":
463
modules = ["mypackage.core", "mypackage.utils", "mypackage.models"]
464
success = validate_pickling(modules)
465
sys.exit(0 if success else 1)
466
```