1- """Parse a Python module and describe its classes and methods .
1+ """Parse a Python module and describe its classes and functions .
22
33Parse 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.
55
66The interface consists of a single function:
77 readmodule_ex(module [, path])
88where module is the name of a Python module, and path is an optional
99list of directories where the module is to be searched. If present,
1010path is prepended to the system search path sys.path. The return
1111value 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.
1718
1819Classes 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:
2021 module -- the module name
2122 name -- the name of the object
2223 file -- the file in which the object was defined
2324 lineno -- the line in the file on which the definition of the object
2425 started
2526 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 .
2930
3031A 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:
3333 super -- a list of super classes (Class instances)
3434 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.
3736If the name of a super class is not recognized, the corresponding
3837entry in the list of super classes is not a class instance but a
3938string giving the name of the super class. Since import statements
4039are recognized and imported modules are scanned as well, this
4140shouldn't happen often.
4241
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.
4444"""
4545
4646import io
5555
5656
5757class Object :
58- """Class to represent a Python object ."""
58+ """Class to represent a Python class or function ."""
5959 def __init__ (self , module , name , file , lineno , parent ):
6060 self .module = module
6161 self .name = name
6262 self .file = file
6363 self .lineno = lineno
6464 self .parent = parent
65- self .objects = {}
65+ self .children = {}
6666
67- def _addobject (self , name , obj ):
68- self .objects [name ] = obj
67+ def _addchild (self , name , obj ):
68+ self .children [name ] = obj
6969
7070
71- # each Python class is represented by an instance of this class
71+ # Each Python class is represented by an instance of this class.
7272class Class (Object ):
7373 '''Class to represent a Python class.'''
7474 def __init__ (self , module , name , super , file , lineno , parent = None ):
7575 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
7977 self .methods = {}
8078
8179 def _addmethod (self , name , lineno ):
@@ -127,7 +125,7 @@ def _readmodule(module, path, inpackage=None):
127125 package search path; otherwise, we are searching for a top-level
128126 module, and PATH is combined with sys.path.
129127 '''
130- # Compute the full module name (prepending inpackage if set)
128+ # Compute the full module name (prepending inpackage if set).
131129 if inpackage is not None :
132130 fullmodule = "%s.%s" % (inpackage , module )
133131 else :
@@ -137,15 +135,15 @@ def _readmodule(module, path, inpackage=None):
137135 if fullmodule in _modules :
138136 return _modules [fullmodule ]
139137
140- # Initialize the dict for this module's contents
141- dict = {}
138+ # Initialize the dict for this module's contents.
139+ tree = {}
142140
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.
144142 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
147145
148- # Check for a dotted module name
146+ # Check for a dotted module name.
149147 i = module .rfind ('.' )
150148 if i >= 0 :
151149 package = module [:i ]
@@ -157,27 +155,26 @@ def _readmodule(module, path, inpackage=None):
157155 raise ImportError ('No package named {}' .format (package ))
158156 return _readmodule (submodule , parent ['__path__' ], package )
159157
160- # Search the path for the module
158+ # Search the path for the module.
161159 f = None
162160 if inpackage is not None :
163161 search_path = path
164162 else :
165163 search_path = path + sys .path
166164 spec = importlib .util ._find_spec_from_path (fullmodule , search_path )
167- _modules [fullmodule ] = dict
165+ _modules [fullmodule ] = tree
168166 # is module a package?
169167 if spec .submodule_search_locations is not None :
170- dict ['__path__' ] = spec .submodule_search_locations
168+ tree ['__path__' ] = spec .submodule_search_locations
171169 try :
172170 source = spec .loader .get_source (fullmodule )
173171 if source is None :
174- return dict
172+ return tree
175173 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
178176
179177 fname = spec .loader .get_filename (fullmodule )
180-
181178 f = io .StringIO (source )
182179
183180 stack = [] # stack of (class, indent) pairs
@@ -195,34 +192,34 @@ def _readmodule(module, path, inpackage=None):
195192 # close previous nested classes and defs
196193 while stack and stack [- 1 ][1 ] >= thisindent :
197194 del stack [- 1 ]
198- tokentype , meth_name , start = next (g )[0 :3 ]
195+ tokentype , func_name , start = next (g )[0 :3 ]
199196 if tokentype != NAME :
200197 continue # Syntax error
201198 cur_func = None
202199 if stack :
203200 cur_obj = stack [- 1 ][0 ]
204201 if isinstance (cur_obj , Object ):
205202 # 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 )
208205
209206 if isinstance (cur_obj , Class ):
210207 # it's a method
211- cur_obj ._addmethod (meth_name , lineno )
208+ cur_obj ._addmethod (func_name , lineno )
212209 else :
213210 # 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
216213 stack .append ((cur_func , thisindent )) # Marker for nested fns.
217214 elif token == 'class' :
218215 lineno , thisindent = start
219- # close previous nested classes and defs
216+ # Close previous nested classes and defs.
220217 while stack and stack [- 1 ][1 ] >= thisindent :
221218 del stack [- 1 ]
222219 tokentype , class_name , start = next (g )[0 :3 ]
223220 if tokentype != NAME :
224221 continue # Syntax error
225- # parse what follows the class name
222+ # Parse what follows the class name.
226223 tokentype , token , start = next (g )[0 :3 ]
227224 inherit = None
228225 if token == '(' :
@@ -234,9 +231,9 @@ def _readmodule(module, path, inpackage=None):
234231 tokentype , token , start = next (g )[0 :3 ]
235232 if token in (')' , ',' ) and level == 1 :
236233 n = "" .join (super )
237- if n in dict :
234+ if n in tree :
238235 # we know this super class
239- n = dict [n ]
236+ n = tree [n ]
240237 else :
241238 c = n .split ('.' )
242239 if len (c ) > 1 :
@@ -270,11 +267,11 @@ def _readmodule(module, path, inpackage=None):
270267 # Either a nested class or a class inside a function.
271268 cur_class = _newclass (cur_obj , class_name , inherit ,
272269 lineno )
273- cur_obj ._addobject (class_name , cur_class )
270+ cur_obj ._addchild (class_name , cur_class )
274271 else :
275272 cur_class = Class (fullmodule , class_name , inherit ,
276273 fname , lineno )
277- dict [class_name ] = cur_class
274+ tree [class_name ] = cur_class
278275 stack .append ((cur_class , thisindent ))
279276 elif token == 'import' and start [1 ] == 0 :
280277 modules = _getnamelist (g )
@@ -298,27 +295,27 @@ def _readmodule(module, path, inpackage=None):
298295 continue
299296 names = _getnamelist (g )
300297 try :
301- # Recursively read the imported module
298+ # Recursively read the imported module.
302299 d = _readmodule (mod , path , inpackage )
303300 except :
304301 # If we can't find or parse the imported module,
305302 # too bad -- don't die here.
306303 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.
309306 for n , n2 in names :
310307 if n in d :
311- dict [n2 or n ] = d [n ]
308+ tree [n2 or n ] = d [n ]
312309 elif n == '*' :
313310 # don't add names that start with _
314311 for n in d :
315312 if n [0 ] != '_' :
316- dict [n ] = d [n ]
313+ tree [n ] = d [n ]
317314 except StopIteration :
318315 pass
319316
320317 f .close ()
321- return dict
318+ return tree
322319
323320
324321def _getnamelist (g ):
@@ -365,17 +362,20 @@ def _getname(g):
365362def _main ():
366363 # Main program for testing.
367364 import os
368- mod = sys .argv [1 ]
365+ try :
366+ mod = sys .argv [1 ]
367+ except :
368+ mod = __file__
369369 if os .path .exists (mod ):
370370 path = [os .path .dirname (mod )]
371371 mod = os .path .basename (mod )
372372 if mod .lower ().endswith (".py" ):
373373 mod = mod [:- 3 ]
374374 else :
375375 path = []
376- dict = readmodule_ex (mod , path )
376+ tree = readmodule_ex (mod , path )
377377 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 )
379379 indent_level = 2
380380 while objs :
381381 obj = objs .pop ()
@@ -386,7 +386,7 @@ def _main():
386386 obj .indent = 0
387387
388388 if isinstance (obj , Object ):
389- new_objs = sorted (obj .objects .values (),
389+ new_objs = sorted (obj .children .values (),
390390 key = lineno_key , reverse = True )
391391 for ob in new_objs :
392392 ob .indent = obj .indent + indent_level
0 commit comments