0
# Source Code Analysis
1
2
dill provides powerful tools for extracting, analyzing, and manipulating source code from Python objects, enabling introspection, code generation, and dynamic analysis capabilities.
3
4
## Source Code Extraction
5
6
### Primary Source Functions
7
8
```python { .api }
9
def getsource(object, alias='', lstrip=False, enclosing=False, force=False, builtin=False):
10
"""
11
Get source code for an object.
12
13
Extracts the source code of functions, classes, methods, and other
14
code objects, providing string representation of the original code.
15
The source code for interactively-defined objects are extracted from
16
the interpreter's history.
17
18
Parameters:
19
- object: object to get source for (module, class, method, function, traceback, frame, or code object)
20
- alias: str, alias name for the object (adds line of code that renames the object)
21
- lstrip: bool, ensure there is no indentation in the first line of code
22
- enclosing: bool, include enclosing code and dependencies
23
- force: bool, catch (TypeError,IOError) and try to use import hooks
24
- builtin: bool, force an import for any builtins
25
26
Returns:
27
str: source code as single string
28
29
Raises:
30
- IOError: when source code cannot be retrieved
31
- TypeError: for objects where source code is unavailable (e.g. builtins)
32
"""
33
34
def getsourcelines(object, lstrip=False, enclosing=False):
35
"""
36
Get source lines for an object.
37
38
Returns the source code as a list of lines along with the starting
39
line number in the original file.
40
41
Parameters:
42
- object: object to get source lines for
43
- lstrip: bool, left-strip whitespace from source lines
44
- enclosing: bool, include enclosing scope
45
46
Returns:
47
tuple: (list of source lines, starting line number)
48
49
Raises:
50
- OSError: when source cannot be found
51
"""
52
53
def findsource(object):
54
"""
55
Find source code for an object.
56
57
Locate the source code for an object by examining the file system,
58
module imports, and other available sources.
59
60
Parameters:
61
- object: object to find source for
62
63
Returns:
64
tuple: (list of all source lines, starting line number)
65
66
Raises:
67
- OSError: when source cannot be located
68
"""
69
```
70
71
### Source Code Utilities
72
73
```python { .api }
74
def getblocks(object, lstrip=False, enclosing=False, locate=False):
75
"""
76
Get code blocks for an object.
77
78
Extracts logical code blocks (functions, classes, methods) associated
79
with an object, providing structured access to code components.
80
81
Parameters:
82
- object: object to get code blocks for
83
- lstrip: bool, left-strip whitespace from blocks
84
- enclosing: bool, include enclosing blocks
85
- locate: bool, include location information
86
87
Returns:
88
list: list of code blocks with metadata
89
"""
90
91
def dumpsource(object, alias='', new=False, enclose=True):
92
"""
93
Dump source code with additional metadata.
94
95
Creates a comprehensive source dump including dependencies,
96
imports, and context information needed for code reconstruction.
97
98
Parameters:
99
- object: object to dump source for
100
- alias: str, alias name for the object
101
- new: bool, create new-style source dump format
102
- enclose: bool, enclose source with additional context
103
104
Returns:
105
str: comprehensive source dump
106
"""
107
```
108
109
## Import Analysis
110
111
### Import Statement Generation
112
113
```python { .api }
114
def getimport(obj, alias='', verify=True, builtin=False, enclosing=False):
115
"""
116
Get the likely import string for the given object.
117
118
Generates the appropriate import statement needed to access an object,
119
including module path resolution and alias handling.
120
121
Parameters:
122
- obj: object to inspect and generate import for
123
- alias: str, alias name to use (renames the object on import)
124
- verify: bool, test the import string before returning it
125
- builtin: bool, force an import for builtins where possible
126
- enclosing: bool, get the import for the outermost enclosing callable
127
128
Returns:
129
str: import statement as string
130
"""
131
132
def getimportable(obj, alias='', byname=True, explicit=False):
133
"""
134
Get importable representation of an object.
135
136
Creates a representation that can be used to recreate the object
137
through import statements and attribute access.
138
139
Parameters:
140
- obj: object to make importable
141
- alias: str, alias name to use
142
- byname: bool, prefer name-based imports over direct references
143
- explicit: bool, use explicit import paths
144
145
Returns:
146
str: importable representation
147
"""
148
149
def likely_import(obj, passive=False, explicit=False):
150
"""
151
Get likely import statement for an object.
152
153
Attempts to determine the most likely import statement that would
154
provide access to the given object.
155
156
Parameters:
157
- obj: object to analyze
158
- passive: bool, use passive analysis without side effects
159
- explicit: bool, prefer explicit import statements
160
161
Returns:
162
str: likely import statement
163
"""
164
```
165
166
### Object Identification
167
168
```python { .api }
169
def getname(obj, force=False, fqn=False):
170
"""
171
Get the name of an object.
172
173
Attempts to determine the canonical name of an object by examining
174
its attributes, module context, and definition location.
175
176
Parameters:
177
- obj: object to get name for
178
- force: bool, force name extraction even if not directly available
179
- fqn: bool, return fully qualified name including module path
180
181
Returns:
182
str: object name or None if name cannot be determined
183
"""
184
185
def isfrommain(obj):
186
"""
187
Check if object is from __main__ module.
188
189
Determines whether an object originates from the main module,
190
which affects how it should be serialized and imported.
191
192
Parameters:
193
- obj: object to check
194
195
Returns:
196
bool: True if object is from __main__ module
197
"""
198
199
def isdynamic(obj):
200
"""
201
Check if object is dynamically created.
202
203
Determines whether an object was created dynamically at runtime
204
rather than being defined in source code.
205
206
Parameters:
207
- obj: object to check
208
209
Returns:
210
bool: True if object is dynamically created
211
"""
212
```
213
214
## Usage Examples
215
216
### Basic Source Extraction
217
218
```python
219
import dill.source as source
220
221
# Define a function
222
def example_function(x, y=10):
223
"""Example function with default parameter."""
224
result = x + y
225
return result * 2
226
227
# Get source code
228
source_code = source.getsource(example_function)
229
print(source_code)
230
# Output: Full function definition as string
231
232
# Get source lines with line numbers
233
lines, start_line = source.getsourcelines(example_function)
234
print(f"Function starts at line {start_line}")
235
for i, line in enumerate(lines):
236
print(f"{start_line + i}: {line.rstrip()}")
237
```
238
239
### Advanced Source Analysis
240
241
```python
242
import dill.source as source
243
244
class ExampleClass:
245
"""Example class for source analysis."""
246
247
def __init__(self, value):
248
self.value = value
249
250
def method(self, multiplier=2):
251
return self.value * multiplier
252
253
@staticmethod
254
def static_method(x):
255
return x ** 2
256
257
# Analyze class source
258
class_source = source.getsource(ExampleClass)
259
print("Class source:")
260
print(class_source)
261
262
# Get method source separately
263
method_source = source.getsource(ExampleClass.method)
264
print("\nMethod source:")
265
print(method_source)
266
267
# Get source blocks
268
blocks = source.getblocks(ExampleClass)
269
print(f"\nFound {len(blocks)} code blocks in class")
270
```
271
272
### Import Generation
273
274
```python
275
import dill.source as source
276
import json
277
from collections import defaultdict
278
279
# Generate import statements for various objects
280
objects_to_import = [
281
json.dumps,
282
defaultdict,
283
ExampleClass,
284
example_function
285
]
286
287
for obj in objects_to_import:
288
try:
289
import_stmt = source.getimport(obj)
290
name = source.getname(obj) or str(obj)
291
print(f"{name}: {import_stmt}")
292
except Exception as e:
293
print(f"Could not generate import for {obj}: {e}")
294
295
# Generate imports with aliases
296
alias_import = source.getimport(json.dumps, alias='json_serialize')
297
print(f"With alias: {alias_import}")
298
```
299
300
### Dynamic Code Analysis
301
302
```python
303
import dill.source as source
304
305
# Create dynamic function
306
exec('''
307
def dynamic_function(x):
308
return x * 3 + 1
309
''')
310
311
# Analyze dynamic objects
312
if source.isdynamic(dynamic_function):
313
print("Function was created dynamically")
314
315
if source.isfrommain(dynamic_function):
316
print("Function is from __main__ module")
317
318
# Try to get source of dynamic function
319
try:
320
dynamic_source = source.getsource(dynamic_function)
321
print("Dynamic function source:")
322
print(dynamic_source)
323
except OSError:
324
print("Cannot get source for dynamic function")
325
```
326
327
### Source Code Modification
328
329
```python
330
import dill.source as source
331
332
def modify_function_source(func, modifications):
333
"""Modify function source code."""
334
try:
335
# Get original source
336
original_source = source.getsource(func)
337
338
# Apply modifications
339
modified_source = original_source
340
for old, new in modifications.items():
341
modified_source = modified_source.replace(old, new)
342
343
# Create new function from modified source
344
namespace = {}
345
exec(modified_source, namespace)
346
347
# Find the function in the namespace
348
func_name = source.getname(func)
349
if func_name in namespace:
350
return namespace[func_name]
351
352
return None
353
354
except Exception as e:
355
print(f"Failed to modify function: {e}")
356
return None
357
358
# Example usage
359
def original_add(a, b):
360
return a + b
361
362
# Modify to multiply instead
363
modifications = {'a + b': 'a * b'}
364
modified_func = modify_function_source(original_add, modifications)
365
366
if modified_func:
367
print(f"Original: {original_add(3, 4)}") # 7
368
print(f"Modified: {modified_func(3, 4)}") # 12
369
```
370
371
## Advanced Features
372
373
### Source Code Archival
374
375
```python
376
import dill.source as source
377
import json
378
379
def archive_source_code(objects, filename):
380
"""Archive source code for multiple objects."""
381
archive = {}
382
383
for name, obj in objects.items():
384
try:
385
obj_info = {
386
'source': source.getsource(obj),
387
'import': source.getimport(obj, verify=False),
388
'name': source.getname(obj),
389
'is_dynamic': source.isdynamic(obj),
390
'is_from_main': source.isfrommain(obj)
391
}
392
archive[name] = obj_info
393
except Exception as e:
394
archive[name] = {'error': str(e)}
395
396
with open(filename, 'w') as f:
397
json.dump(archive, f, indent=2)
398
399
return archive
400
401
# Archive important functions and classes
402
objects_to_archive = {
403
'example_function': example_function,
404
'ExampleClass': ExampleClass,
405
'json_dumps': json.dumps
406
}
407
408
archive = archive_source_code(objects_to_archive, 'source_archive.json')
409
print(f"Archived {len(archive)} objects")
410
```
411
412
### Code Dependency Analysis
413
414
```python
415
import dill.source as source
416
import dill.detect as detect
417
import ast
418
419
def analyze_dependencies(func):
420
"""Analyze function dependencies."""
421
try:
422
# Get source code
423
source_code = source.getsource(func)
424
425
# Parse AST
426
tree = ast.parse(source_code)
427
428
# Find imports and name references
429
dependencies = {
430
'imports': [],
431
'globals': [],
432
'builtins': []
433
}
434
435
for node in ast.walk(tree):
436
if isinstance(node, ast.Import):
437
for alias in node.names:
438
dependencies['imports'].append(alias.name)
439
elif isinstance(node, ast.ImportFrom):
440
module = node.module or ''
441
for alias in node.names:
442
dependencies['imports'].append(f"{module}.{alias.name}")
443
444
# Get global variables referenced
445
global_vars = detect.referredglobals(func, recurse=True)
446
dependencies['globals'] = list(global_vars.keys())
447
448
return dependencies
449
450
except Exception as e:
451
return {'error': str(e)}
452
453
# Analyze function dependencies
454
deps = analyze_dependencies(example_function)
455
print("Function dependencies:")
456
for dep_type, items in deps.items():
457
if items:
458
print(f" {dep_type}: {items}")
459
```
460
461
### Interactive Source Browser
462
463
```python
464
import dill.source as source
465
466
class SourceBrowser:
467
"""Interactive source code browser."""
468
469
def __init__(self):
470
self.history = []
471
472
def browse(self, obj):
473
"""Browse source code for an object."""
474
try:
475
obj_name = source.getname(obj) or str(obj)
476
self.history.append(obj_name)
477
478
print(f"\n{'='*60}")
479
print(f"Source for {obj_name}")
480
print(f"{'='*60}")
481
482
# Show basic info
483
print(f"Type: {type(obj).__name__}")
484
print(f"From main: {source.isfrommain(obj)}")
485
print(f"Dynamic: {source.isdynamic(obj)}")
486
487
# Show import statement
488
try:
489
import_stmt = source.getimport(obj, verify=False)
490
print(f"Import: {import_stmt}")
491
except:
492
print("Import: Not available")
493
494
print(f"\nSource code:")
495
print("-" * 40)
496
497
# Show source
498
source_code = source.getsource(obj)
499
print(source_code)
500
501
except Exception as e:
502
print(f"Error browsing {obj}: {e}")
503
504
def show_history(self):
505
"""Show browsing history."""
506
print("\nBrowsing history:")
507
for i, item in enumerate(self.history):
508
print(f" {i+1}. {item}")
509
510
# Usage
511
browser = SourceBrowser()
512
browser.browse(example_function)
513
browser.browse(ExampleClass)
514
browser.show_history()
515
```
516
517
## Integration with Serialization
518
519
### Source-Aware Pickling
520
521
```python
522
import dill
523
import dill.source as source
524
525
def pickle_with_source(obj, filename):
526
"""Pickle object along with its source code."""
527
# Create enhanced object with source
528
enhanced_obj = {
529
'object': obj,
530
'metadata': {
531
'source': None,
532
'import': None,
533
'name': source.getname(obj),
534
'type': type(obj).__name__
535
}
536
}
537
538
# Try to get source and import info
539
try:
540
enhanced_obj['metadata']['source'] = source.getsource(obj)
541
except:
542
pass
543
544
try:
545
enhanced_obj['metadata']['import'] = source.getimport(obj, verify=False)
546
except:
547
pass
548
549
# Pickle enhanced object
550
with open(filename, 'wb') as f:
551
dill.dump(enhanced_obj, f)
552
553
def unpickle_with_source(filename):
554
"""Unpickle object and display source information."""
555
with open(filename, 'rb') as f:
556
enhanced_obj = dill.load(f)
557
558
obj = enhanced_obj['object']
559
metadata = enhanced_obj['metadata']
560
561
print(f"Loaded {metadata['type']}: {metadata['name']}")
562
563
if metadata['import']:
564
print(f"Import: {metadata['import']}")
565
566
if metadata['source']:
567
print("Source code:")
568
print(metadata['source'])
569
570
return obj
571
572
# Usage
573
pickle_with_source(example_function, 'function_with_source.pkl')
574
restored_func = unpickle_with_source('function_with_source.pkl')
575
```