从模块中获取 类 而实际上不必 运行 文件

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

继续 ,我现在的目标是解析 Python 文件,然后

  1. 提取所有classes
  2. 提取其属性列表和碱基列表classes

没有加载文件(运行它)。

目前,我有这个工作代码:

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)

待解析文件:

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

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


def func():
    print('Hello')

这个解决方案的好处是我得到了 class 名称的列表,即使文件 a.py 包含无效的 python 代码。看来我必须更深入地研究 AST 模块。有什么方法可以提取 class 属性列表及其基础 classes 吗?

一个 ClassDef 节点有一个 bases 属性,它是代表 class 的基础 classes 的节点列表。它还有一个 body 属性,它是表示 class 定义的 body 的节点列表。我想您想要 body 中的 Assign 节点,但也许您的意思是 class 属性略有不同。

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

可以使用递归遍历ast.parse产生的ast。下面的解决方案不仅在主输入文件中而且在任何后续导入的文件中执行此搜索:

import ast, importlib
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.ImportFrom, ast.Import)):
         for i in (k if isinstance((k:=getattr(tree, 'module', tree.names)), list) else [k]):
             with open(importlib.machinery.PathFinder().find_module(getattr(i, 'name', i)).get_filename()) as f:
                self.walk(ast.parse(f.read()))
      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)

将其与您的两个原始文件放在一起:

文件c.py:

c_var = 2

class CClass:
   name = 'Anna'

文件a.py:

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

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


def func():
    print('Hello')
p = Parse()
with open('a_mod_test.py') as f:
   p.walk(ast.parse(f.read()))

print(p.c)

输出:

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