Home > Enterprise >  Fetching classes from a module without actually having to run the file
Fetching classes from a module without actually having to run the file

Time:10-18

Going on this question of mine, my goal now is to parse a Python file, and to

  1. Extract all classes
  2. Extract the list of its attributes and list of bases classes

Without loading the file (running it).

Currently, I have this working code:

parser.py

import ast

def get_classes(path):
    with open(path) as fh:        
       root = ast.parse(fh.read(), path)
    classes = []
    for node in ast.iter_child_nodes(root):
        if isinstance(node, ast.ClassDef):
            classes.append(node.name)
        else: 
            continue
    return classes
    
for c in get_classes('a.py'):
    print(c)

File to be parsed:

from c import CClass
    
class MyClass(UndefinedClass):
    name = 'Edgar'

    def foo(self, x):
        print(x)


def func():
    print('Hello')

The good part of this solution is that I get the list of class names even given that file a.py contains invalid python code. Looks like I have to dig deeper into AST module. Is there any way I can extract the list of class attributes and its base classes ?

CodePudding user response:

A ClassDef node has a bases attribute which is a list of nodes representing the base classes for the class. It also has a body attribute which is a list of nodes representing the body of the class definition. I suppose you want the Assign nodes in the body, but maybe you mean something slightly different by class attributes.

https://docs.python.org/3/library/ast.html#ast.ClassDef

CodePudding user response:

You can use recursion to traverse the ast produced by ast.parse:

import ast
class Parse:
   def __init__(self):
      self.c = {}
   def walk(self, tree, f = None):
      if isinstance(tree, ast.ClassDef):
         self.c[tree.name] = {'bases':[i.id for i in tree.bases], 'attrs':[]}
         for i in tree.body:
             self.walk(i, tree.name)
      elif isinstance(tree, ast.Assign) and f is not None:
         self.c[f]['attrs'].append(tree.targets[0].id)
      else:
         for i in getattr(tree, '_fields', []):
            for j in (k if isinstance((k:=getattr(tree, i)), list) else [k]):
               self.walk(j, None)

s = """
from c import CClass
    
class MyClass(UndefinedClass):
    name = 'Edgar'

    def foo(self, x):
        print(x)


def func():
    print('Hello')
"""
p = Parse()
p.walk(ast.parse(s))
print(p.c)

Output:

{'MyClass': {'bases': ['UndefinedClass'], 'attrs': ['name']}}
  • Related