1515is present for packages: the key '__path__' has a list as its value
1616which contains the package search path.
1717
18- A class is described by the class Class in this module. Instances
19- of this class have the following instance variables:
20- module -- the module name
21- name -- the name of the class
22- super -- a list of super classes (Class instances)
18+ 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+ module -- the module name
21+ name -- the name of the object
22+ file -- the file in which the object was defined
23+ lineno -- the line in the file on which the definition of the object
24+ started
25+ 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.
29+
30+ 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):
33+ super -- a list of super classes (Class instances)
2334 methods -- a dictionary of methods
24- file -- the file in which the class was defined
25- lineno -- the line in the file on which the class statement occurred
2635The dictionary of methods uses the method names as keys and the line
2736numbers on which the method was defined as values.
2837If the name of a super class is not recognized, the corresponding
3241shouldn't happen often.
3342
3443A function is described by the class Function in this module.
35- Instances of this class have the following instance variables:
36- module -- the module name
37- name -- the name of the class
38- file -- the file in which the class was defined
39- lineno -- the line in the file on which the class statement occurred
4044"""
4145
4246import io
4549import tokenize
4650from token import NAME , DEDENT , OP
4751
48- __all__ = ["readmodule" , "readmodule_ex" , "Class" , "Function" ]
52+ __all__ = ["readmodule" , "readmodule_ex" , "Object" , " Class" , "Function" ]
4953
5054_modules = {} # cache of modules we've seen
5155
52- # each Python class is represented by an instance of this class
53- class Class :
54- ''' Class to represent a Python class.'''
55- def __init__ (self , module , name , super , file , lineno ):
56+
57+ class Object :
58+ """ Class to represent a Python object."""
59+ def __init__ (self , module , name , file , lineno , parent ):
5660 self .module = module
5761 self .name = name
62+ self .file = file
63+ self .lineno = lineno
64+ self .parent = parent
65+ self .objects = {}
66+
67+ def _addobject (self , name , obj ):
68+ self .objects [name ] = obj
69+
70+
71+ # each Python class is represented by an instance of this class
72+ class Class (Object ):
73+ '''Class to represent a Python class.'''
74+ def __init__ (self , module , name , super , file , lineno , parent = None ):
75+ Object .__init__ (self , module , name , file , lineno , parent )
5876 if super is None :
5977 super = []
6078 self .super = super
6179 self .methods = {}
62- self .file = file
63- self .lineno = lineno
6480
6581 def _addmethod (self , name , lineno ):
6682 self .methods [name ] = lineno
6783
68- class Function :
84+
85+ class Function (Object ):
6986 '''Class to represent a top-level Python function'''
70- def __init__ (self , module , name , file , lineno ):
71- self .module = module
72- self .name = name
73- self .file = file
74- self .lineno = lineno
87+ def __init__ (self , module , name , file , lineno , parent = None ):
88+ Object .__init__ (self , module , name , file , lineno , parent )
89+
90+
91+ def _newfunction (ob , name , lineno ):
92+ '''Helper function for creating a nested function or a method.'''
93+ return Function (ob .module , name , ob .file , lineno , ob )
94+
95+
96+ def _newclass (ob , name , super , lineno ):
97+ '''Helper function for creating a nested class.'''
98+ return Class (ob .module , name , super , ob .file , lineno , ob )
99+
75100
76101def readmodule (module , path = None ):
77102 '''Backwards compatible interface.
@@ -138,7 +163,6 @@ def _readmodule(module, path, inpackage=None):
138163 search_path = path
139164 else :
140165 search_path = path + sys .path
141- # XXX This will change once issue19944 lands.
142166 spec = importlib .util ._find_spec_from_path (fullmodule , search_path )
143167 _modules [fullmodule ] = dict
144168 # is module a package?
@@ -174,17 +198,22 @@ def _readmodule(module, path, inpackage=None):
174198 tokentype , meth_name , start = next (g )[0 :3 ]
175199 if tokentype != NAME :
176200 continue # Syntax error
201+ cur_func = None
177202 if stack :
178- cur_class = stack [- 1 ][0 ]
179- if isinstance (cur_class , Class ):
180- # it's a method
181- cur_class ._addmethod (meth_name , lineno )
182- # else it's a nested def
203+ cur_obj = stack [- 1 ][0 ]
204+ if isinstance (cur_obj , Object ):
205+ # 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 )
208+
209+ if isinstance (cur_obj , Class ):
210+ # it's a method
211+ cur_obj ._addmethod (meth_name , lineno )
183212 else :
184213 # it's a function
185- dict [ meth_name ] = Function (fullmodule , meth_name ,
186- fname , lineno )
187- stack .append ((None , thisindent )) # Marker for nested fns
214+ cur_func = Function (fullmodule , meth_name , fname , lineno )
215+ dict [ meth_name ] = cur_func
216+ stack .append ((cur_func , thisindent )) # Marker for nested fns.
188217 elif token == 'class' :
189218 lineno , thisindent = start
190219 # close previous nested classes and defs
@@ -235,9 +264,16 @@ def _readmodule(module, path, inpackage=None):
235264 super .append (token )
236265 # expressions in the base list are not supported
237266 inherit = names
238- cur_class = Class (fullmodule , class_name , inherit ,
239- fname , lineno )
240- if not stack :
267+ if stack :
268+ cur_obj = stack [- 1 ][0 ]
269+ if isinstance (cur_obj , Object ):
270+ # Either a nested class or a class inside a function.
271+ cur_class = _newclass (cur_obj , class_name , inherit ,
272+ lineno )
273+ cur_obj ._addobject (class_name , cur_class )
274+ else :
275+ cur_class = Class (fullmodule , class_name , inherit ,
276+ fname , lineno )
241277 dict [class_name ] = cur_class
242278 stack .append ((cur_class , thisindent ))
243279 elif token == 'import' and start [1 ] == 0 :
@@ -284,6 +320,7 @@ def _readmodule(module, path, inpackage=None):
284320 f .close ()
285321 return dict
286322
323+
287324def _getnamelist (g ):
288325 # Helper to get a comma-separated list of dotted names plus 'as'
289326 # clauses. Return a list of pairs (name, name2) where name2 is
@@ -304,6 +341,7 @@ def _getnamelist(g):
304341 break
305342 return names
306343
344+
307345def _getname (g ):
308346 # Helper to get a dotted name, return a pair (name, token) where
309347 # name is the dotted name, or None if there was no dotted name,
@@ -323,10 +361,10 @@ def _getname(g):
323361 parts .append (token )
324362 return ("." .join (parts ), token )
325363
364+
326365def _main ():
327366 # Main program for testing.
328367 import os
329- from operator import itemgetter
330368 mod = sys .argv [1 ]
331369 if os .path .exists (mod ):
332370 path = [os .path .dirname (mod )]
@@ -336,17 +374,28 @@ def _main():
336374 else :
337375 path = []
338376 dict = readmodule_ex (mod , path )
339- objs = list (dict .values ())
340- objs .sort (key = lambda a : getattr (a , 'lineno' , 0 ))
341- for obj in objs :
377+ lineno_key = lambda a : getattr (a , 'lineno' , 0 )
378+ objs = sorted (dict .values (), key = lineno_key , reverse = True )
379+ indent_level = 2
380+ while objs :
381+ obj = objs .pop ()
382+ if isinstance (obj , list ):
383+ # Value of a __path__ key
384+ continue
385+ if not hasattr (obj , 'indent' ):
386+ obj .indent = 0
387+
388+ if isinstance (obj , Object ):
389+ new_objs = sorted (obj .objects .values (),
390+ key = lineno_key , reverse = True )
391+ for ob in new_objs :
392+ ob .indent = obj .indent + indent_level
393+ objs .extend (new_objs )
342394 if isinstance (obj , Class ):
343- print ("class" , obj .name , obj .super , obj .lineno )
344- methods = sorted (obj .methods .items (), key = itemgetter (1 ))
345- for name , lineno in methods :
346- if name != "__path__" :
347- print (" def" , name , lineno )
395+ print ("{}class {} {} {}"
396+ .format (' ' * obj .indent , obj .name , obj .super , obj .lineno ))
348397 elif isinstance (obj , Function ):
349- print ("def" , obj .name , obj .lineno )
398+ print ("{} def {} {}" . format ( ' ' * obj . indent , obj .name , obj .lineno ) )
350399
351400if __name__ == "__main__" :
352401 _main ()
0 commit comments