15
15
is present for packages: the key '__path__' has a list as its value
16
16
which contains the package search path.
17
17
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)
23
34
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
26
35
The dictionary of methods uses the method names as keys and the line
27
36
numbers on which the method was defined as values.
28
37
If the name of a super class is not recognized, the corresponding
32
41
shouldn't happen often.
33
42
34
43
A 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
40
44
"""
41
45
42
46
import io
45
49
import tokenize
46
50
from token import NAME , DEDENT , OP
47
51
48
- __all__ = ["readmodule" , "readmodule_ex" , "Class" , "Function" ]
52
+ __all__ = ["readmodule" , "readmodule_ex" , "Object" , " Class" , "Function" ]
49
53
50
54
_modules = {} # cache of modules we've seen
51
55
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 ):
56
60
self .module = module
57
61
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 )
58
76
if super is None :
59
77
super = []
60
78
self .super = super
61
79
self .methods = {}
62
- self .file = file
63
- self .lineno = lineno
64
80
65
81
def _addmethod (self , name , lineno ):
66
82
self .methods [name ] = lineno
67
83
68
- class Function :
84
+
85
+ class Function (Object ):
69
86
'''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
+
75
100
76
101
def readmodule (module , path = None ):
77
102
'''Backwards compatible interface.
@@ -138,7 +163,6 @@ def _readmodule(module, path, inpackage=None):
138
163
search_path = path
139
164
else :
140
165
search_path = path + sys .path
141
- # XXX This will change once issue19944 lands.
142
166
spec = importlib .util ._find_spec_from_path (fullmodule , search_path )
143
167
_modules [fullmodule ] = dict
144
168
# is module a package?
@@ -174,17 +198,22 @@ def _readmodule(module, path, inpackage=None):
174
198
tokentype , meth_name , start = next (g )[0 :3 ]
175
199
if tokentype != NAME :
176
200
continue # Syntax error
201
+ cur_func = None
177
202
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 )
183
212
else :
184
213
# 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.
188
217
elif token == 'class' :
189
218
lineno , thisindent = start
190
219
# close previous nested classes and defs
@@ -235,9 +264,16 @@ def _readmodule(module, path, inpackage=None):
235
264
super .append (token )
236
265
# expressions in the base list are not supported
237
266
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 )
241
277
dict [class_name ] = cur_class
242
278
stack .append ((cur_class , thisindent ))
243
279
elif token == 'import' and start [1 ] == 0 :
@@ -284,6 +320,7 @@ def _readmodule(module, path, inpackage=None):
284
320
f .close ()
285
321
return dict
286
322
323
+
287
324
def _getnamelist (g ):
288
325
# Helper to get a comma-separated list of dotted names plus 'as'
289
326
# clauses. Return a list of pairs (name, name2) where name2 is
@@ -304,6 +341,7 @@ def _getnamelist(g):
304
341
break
305
342
return names
306
343
344
+
307
345
def _getname (g ):
308
346
# Helper to get a dotted name, return a pair (name, token) where
309
347
# name is the dotted name, or None if there was no dotted name,
@@ -323,10 +361,10 @@ def _getname(g):
323
361
parts .append (token )
324
362
return ("." .join (parts ), token )
325
363
364
+
326
365
def _main ():
327
366
# Main program for testing.
328
367
import os
329
- from operator import itemgetter
330
368
mod = sys .argv [1 ]
331
369
if os .path .exists (mod ):
332
370
path = [os .path .dirname (mod )]
@@ -336,17 +374,28 @@ def _main():
336
374
else :
337
375
path = []
338
376
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 )
342
394
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 ))
348
397
elif isinstance (obj , Function ):
349
- print ("def" , obj .name , obj .lineno )
398
+ print ("{} def {} {}" . format ( ' ' * obj . indent , obj .name , obj .lineno ) )
350
399
351
400
if __name__ == "__main__" :
352
401
_main ()
0 commit comments