1
- """Parse a Python module and describe its classes and methods .
1
+ """Parse a Python module and describe its classes and functions .
2
2
3
3
Parse enough of a Python file to recognize imports and class and
4
- method definitions, and to find out the superclasses of a class.
4
+ function definitions, and to find out the superclasses of a class.
5
5
6
6
The interface consists of a single function:
7
7
readmodule_ex(module [, path])
8
8
where module is the name of a Python module, and path is an optional
9
9
list of directories where the module is to be searched. If present,
10
10
path is prepended to the system search path sys.path. The return
11
11
value is a dictionary. The keys of the dictionary are the names of
12
- the classes defined in the module (including classes that are defined
13
- via the from XXX import YYY construct). The values are class
14
- instances of the class Class defined here. One special key/value pair
15
- is present for packages: the key '__path__' has a list as its value
16
- which contains the package search path.
12
+ the classes and functions defined in the module (including classes that
13
+ are defined via the from XXX import YYY construct). The values are class
14
+ instances of the class Class and function instances of the class Function,
15
+ respectively. One special key/value pair is present for packages: the
16
+ key '__path__' has a list as its value which contains the package search
17
+ path.
17
18
18
19
Classes and functions have a common superclass in this module, the Object
19
- class. Every instance of this class have the following instance variables:
20
+ class. Every instance of this class has the following instance variables:
20
21
module -- the module name
21
22
name -- the name of the object
22
23
file -- the file in which the object was defined
23
24
lineno -- the line in the file on which the definition of the object
24
25
started
25
26
parent -- the parent of this object, if any
26
- objects -- the other classes and function this object may contain
27
- The 'objects' attribute is a dictionary where each key/value pair corresponds
28
- to the name of the object and the object itself .
27
+ children -- the nested objects ( classes and functions) contained
28
+ in this object
29
+ The 'children' attribute is a dictionary mapping object names to objects .
29
30
30
31
A class is described by the class Class in this module. Instances
31
- of this class have the following instance variables (plus the ones from
32
- Object):
32
+ of this class have the attributes from Object, plus the following:
33
33
super -- a list of super classes (Class instances)
34
34
methods -- a dictionary of methods
35
- The dictionary of methods uses the method names as keys and the line
36
- numbers on which the method was defined as values.
35
+ 'methods' maps method names to the line number where the definition begins.
37
36
If the name of a super class is not recognized, the corresponding
38
37
entry in the list of super classes is not a class instance but a
39
38
string giving the name of the super class. Since import statements
40
39
are recognized and imported modules are scanned as well, this
41
40
shouldn't happen often.
42
41
43
- A function is described by the class Function in this module.
42
+ A function is described by the class Function in this module. The
43
+ only instance attributes are those of Object.
44
44
"""
45
45
46
46
import io
55
55
56
56
57
57
class Object :
58
- """Class to represent a Python object ."""
58
+ """Class to represent a Python class or function ."""
59
59
def __init__ (self , module , name , file , lineno , parent ):
60
60
self .module = module
61
61
self .name = name
62
62
self .file = file
63
63
self .lineno = lineno
64
64
self .parent = parent
65
- self .objects = {}
65
+ self .children = {}
66
66
67
- def _addobject (self , name , obj ):
68
- self .objects [name ] = obj
67
+ def _addchild (self , name , obj ):
68
+ self .children [name ] = obj
69
69
70
70
71
- # each Python class is represented by an instance of this class
71
+ # Each Python class is represented by an instance of this class.
72
72
class Class (Object ):
73
73
'''Class to represent a Python class.'''
74
74
def __init__ (self , module , name , super , file , lineno , parent = None ):
75
75
Object .__init__ (self , module , name , file , lineno , parent )
76
- if super is None :
77
- super = []
78
- self .super = super
76
+ self .super = [] if super is None else super
79
77
self .methods = {}
80
78
81
79
def _addmethod (self , name , lineno ):
@@ -127,7 +125,7 @@ def _readmodule(module, path, inpackage=None):
127
125
package search path; otherwise, we are searching for a top-level
128
126
module, and PATH is combined with sys.path.
129
127
'''
130
- # Compute the full module name (prepending inpackage if set)
128
+ # Compute the full module name (prepending inpackage if set).
131
129
if inpackage is not None :
132
130
fullmodule = "%s.%s" % (inpackage , module )
133
131
else :
@@ -137,15 +135,15 @@ def _readmodule(module, path, inpackage=None):
137
135
if fullmodule in _modules :
138
136
return _modules [fullmodule ]
139
137
140
- # Initialize the dict for this module's contents
141
- dict = {}
138
+ # Initialize the dict for this module's contents.
139
+ tree = {}
142
140
143
- # Check if it is a built-in module; we don't do much for these
141
+ # Check if it is a built-in module; we don't do much for these.
144
142
if module in sys .builtin_module_names and inpackage is None :
145
- _modules [module ] = dict
146
- return dict
143
+ _modules [module ] = tree
144
+ return tree
147
145
148
- # Check for a dotted module name
146
+ # Check for a dotted module name.
149
147
i = module .rfind ('.' )
150
148
if i >= 0 :
151
149
package = module [:i ]
@@ -157,27 +155,26 @@ def _readmodule(module, path, inpackage=None):
157
155
raise ImportError ('No package named {}' .format (package ))
158
156
return _readmodule (submodule , parent ['__path__' ], package )
159
157
160
- # Search the path for the module
158
+ # Search the path for the module.
161
159
f = None
162
160
if inpackage is not None :
163
161
search_path = path
164
162
else :
165
163
search_path = path + sys .path
166
164
spec = importlib .util ._find_spec_from_path (fullmodule , search_path )
167
- _modules [fullmodule ] = dict
165
+ _modules [fullmodule ] = tree
168
166
# is module a package?
169
167
if spec .submodule_search_locations is not None :
170
- dict ['__path__' ] = spec .submodule_search_locations
168
+ tree ['__path__' ] = spec .submodule_search_locations
171
169
try :
172
170
source = spec .loader .get_source (fullmodule )
173
171
if source is None :
174
- return dict
172
+ return tree
175
173
except (AttributeError , ImportError ):
176
- # not Python source, can't do anything with this module
177
- return dict
174
+ # not Python source, can't do anything with this module.
175
+ return tree
178
176
179
177
fname = spec .loader .get_filename (fullmodule )
180
-
181
178
f = io .StringIO (source )
182
179
183
180
stack = [] # stack of (class, indent) pairs
@@ -195,34 +192,34 @@ def _readmodule(module, path, inpackage=None):
195
192
# close previous nested classes and defs
196
193
while stack and stack [- 1 ][1 ] >= thisindent :
197
194
del stack [- 1 ]
198
- tokentype , meth_name , start = next (g )[0 :3 ]
195
+ tokentype , func_name , start = next (g )[0 :3 ]
199
196
if tokentype != NAME :
200
197
continue # Syntax error
201
198
cur_func = None
202
199
if stack :
203
200
cur_obj = stack [- 1 ][0 ]
204
201
if isinstance (cur_obj , Object ):
205
202
# It's a nested function or a method.
206
- cur_func = _newfunction (cur_obj , meth_name , lineno )
207
- cur_obj ._addobject ( meth_name , cur_func )
203
+ cur_func = _newfunction (cur_obj , func_name , lineno )
204
+ cur_obj ._addchild ( func_name , cur_func )
208
205
209
206
if isinstance (cur_obj , Class ):
210
207
# it's a method
211
- cur_obj ._addmethod (meth_name , lineno )
208
+ cur_obj ._addmethod (func_name , lineno )
212
209
else :
213
210
# it's a function
214
- cur_func = Function (fullmodule , meth_name , fname , lineno )
215
- dict [ meth_name ] = cur_func
211
+ cur_func = Function (fullmodule , func_name , fname , lineno )
212
+ tree [ func_name ] = cur_func
216
213
stack .append ((cur_func , thisindent )) # Marker for nested fns.
217
214
elif token == 'class' :
218
215
lineno , thisindent = start
219
- # close previous nested classes and defs
216
+ # Close previous nested classes and defs.
220
217
while stack and stack [- 1 ][1 ] >= thisindent :
221
218
del stack [- 1 ]
222
219
tokentype , class_name , start = next (g )[0 :3 ]
223
220
if tokentype != NAME :
224
221
continue # Syntax error
225
- # parse what follows the class name
222
+ # Parse what follows the class name.
226
223
tokentype , token , start = next (g )[0 :3 ]
227
224
inherit = None
228
225
if token == '(' :
@@ -234,9 +231,9 @@ def _readmodule(module, path, inpackage=None):
234
231
tokentype , token , start = next (g )[0 :3 ]
235
232
if token in (')' , ',' ) and level == 1 :
236
233
n = "" .join (super )
237
- if n in dict :
234
+ if n in tree :
238
235
# we know this super class
239
- n = dict [n ]
236
+ n = tree [n ]
240
237
else :
241
238
c = n .split ('.' )
242
239
if len (c ) > 1 :
@@ -270,11 +267,11 @@ def _readmodule(module, path, inpackage=None):
270
267
# Either a nested class or a class inside a function.
271
268
cur_class = _newclass (cur_obj , class_name , inherit ,
272
269
lineno )
273
- cur_obj ._addobject (class_name , cur_class )
270
+ cur_obj ._addchild (class_name , cur_class )
274
271
else :
275
272
cur_class = Class (fullmodule , class_name , inherit ,
276
273
fname , lineno )
277
- dict [class_name ] = cur_class
274
+ tree [class_name ] = cur_class
278
275
stack .append ((cur_class , thisindent ))
279
276
elif token == 'import' and start [1 ] == 0 :
280
277
modules = _getnamelist (g )
@@ -298,27 +295,27 @@ def _readmodule(module, path, inpackage=None):
298
295
continue
299
296
names = _getnamelist (g )
300
297
try :
301
- # Recursively read the imported module
298
+ # Recursively read the imported module.
302
299
d = _readmodule (mod , path , inpackage )
303
300
except :
304
301
# If we can't find or parse the imported module,
305
302
# too bad -- don't die here.
306
303
continue
307
- # add any classes that were defined in the imported module
308
- # to our name space if they were mentioned in the list
304
+ # Add any classes that were defined in the imported module
305
+ # to our name space if they were mentioned in the list.
309
306
for n , n2 in names :
310
307
if n in d :
311
- dict [n2 or n ] = d [n ]
308
+ tree [n2 or n ] = d [n ]
312
309
elif n == '*' :
313
310
# don't add names that start with _
314
311
for n in d :
315
312
if n [0 ] != '_' :
316
- dict [n ] = d [n ]
313
+ tree [n ] = d [n ]
317
314
except StopIteration :
318
315
pass
319
316
320
317
f .close ()
321
- return dict
318
+ return tree
322
319
323
320
324
321
def _getnamelist (g ):
@@ -365,17 +362,20 @@ def _getname(g):
365
362
def _main ():
366
363
# Main program for testing.
367
364
import os
368
- mod = sys .argv [1 ]
365
+ try :
366
+ mod = sys .argv [1 ]
367
+ except :
368
+ mod = __file__
369
369
if os .path .exists (mod ):
370
370
path = [os .path .dirname (mod )]
371
371
mod = os .path .basename (mod )
372
372
if mod .lower ().endswith (".py" ):
373
373
mod = mod [:- 3 ]
374
374
else :
375
375
path = []
376
- dict = readmodule_ex (mod , path )
376
+ tree = readmodule_ex (mod , path )
377
377
lineno_key = lambda a : getattr (a , 'lineno' , 0 )
378
- objs = sorted (dict .values (), key = lineno_key , reverse = True )
378
+ objs = sorted (tree .values (), key = lineno_key , reverse = True )
379
379
indent_level = 2
380
380
while objs :
381
381
obj = objs .pop ()
@@ -386,7 +386,7 @@ def _main():
386
386
obj .indent = 0
387
387
388
388
if isinstance (obj , Object ):
389
- new_objs = sorted (obj .objects .values (),
389
+ new_objs = sorted (obj .children .values (),
390
390
key = lineno_key , reverse = True )
391
391
for ob in new_objs :
392
392
ob .indent = obj .indent + indent_level
0 commit comments