Python 分析项目中的类和函数依赖项
我试图在Python代码库中运行一些类和函数依赖关系的分析。我的第一步是创建一个.csv文件,以便使用Python的Python 分析项目中的类和函数依赖项,python,regex,python-2.6,Python,Regex,Python 2.6,我试图在Python代码库中运行一些类和函数依赖关系的分析。我的第一步是创建一个.csv文件,以便使用Python的csv模块和正则表达式导入Excel 我拥有的当前版本如下所示: import re import os import csv from os.path import join class ClassParser(object): class_expr = re.compile(r'class (.+?)(?:\((.+?)\))?:')
csv
模块和正则表达式导入Excel
我拥有的当前版本如下所示:
import re
import os
import csv
from os.path import join
class ClassParser(object):
class_expr = re.compile(r'class (.+?)(?:\((.+?)\))?:')
python_file_expr = re.compile(r'^\w+[.]py$')
def findAllClasses(self, python_file):
""" Read in a python file and return all the class names
"""
with open(python_file) as infile:
everything = infile.read()
class_names = ClassParser.class_expr.findall(everything)
return class_names
def findAllPythonFiles(self, directory):
""" Find all the python files starting from a top level directory
"""
python_files = []
for root, dirs, files in os.walk(directory):
for file in files:
if ClassParser.python_file_expr.match(file):
python_files.append(join(root,file))
return python_files
def parse(self, directory, output_directory="classes.csv"):
""" Parse the directory and spit out a csv file
"""
with open(output_directory,'w') as csv_file:
writer = csv.writer(csv_file)
python_files = self.findAllPythonFiles(directory)
for file in python_files:
classes = self.findAllClasses(file)
for classname in classes:
writer.writerow([classname[0], classname[1], file])
if __name__=="__main__":
parser = ClassParser()
parser.parse("/path/to/my/project/main/directory")
这将以以下格式生成.csv输出:
class name, inherited classes (also comma separated), file
class name, inherited classes (also comma separated), file
... etc. ...
除了类名之外,我还想开始解析函数声明和定义。我的问题:有没有更好的方法来获取类名、继承的类名、函数名、参数名等
注意:我考虑过使用Pythonast
模块,但我没有使用它的经验,也不知道如何使用它来获得所需的信息,或者它是否能够做到这一点
编辑:为了回应Martin Thurau对更多信息的要求——我试图解决这个问题的原因是因为我继承了一个相当长(100k+行)的项目,它的模块、类和函数没有明显的结构;它们都以文件集合的形式存在于单个源目录中
一些源文件包含几十个相切相关的类,长度超过10k行,这使得它们很难维护。我开始分析将每个类打包成一个更具内聚性的结构作为基础的相对难度。对于该分析,我关心的一部分是类与文件中其他类的交织程度,以及特定类所依赖的导入或继承类。我已经开始实现这一点。将以下代码放入一个文件中,并运行它,传递要分析的文件或目录的名称。它将打印出它找到的所有类、找到它的文件以及类的基。它不是智能的,因此如果在代码库中定义了两个
Foo
类,它不会告诉您正在使用哪个类,但这只是一个开始
此代码使用pythonast
模块检查.py
文件,并查找所有ClassDef节点。然后,它使用它来打印出其中的一部分-您将需要安装此软件包
$ pip install -e git+https://github.com/srossross/Meta.git#egg=meta
$pip安装-e git+https://github.com/srossross/Meta.git#egg=meta
示例输出,针对
$python class-finder.py/path/to/django特色项目/featureditem/
FeaturedField,../django特征项/featureditem/fields.py,models.BooleanField
SingleFeature,../django特色项目/featureditem/tests.py,models.Model
MultipleFeature,../django特征项/featureditem/tests.py,models.Model
作者,../django-featured-item/featurereditem/tests.py,models.Model
书籍,../django特色项目/featureditem/tests.py,models.Model
FeaturedField,../django特征项/featureditem/tests.py,TestCase
守则:
# class-finder.py
import ast
import csv
import meta
import os
import sys
def find_classes(node, in_file):
if isinstance(node, ast.ClassDef):
yield (node, in_file)
if hasattr(node, 'body'):
for child in node.body:
# `yield from find_classes(child)` in Python 3.x
for x in find_classes(child, in_file): yield x
def print_classes(classes, out):
writer = csv.writer(out)
for cls, in_file in classes:
writer.writerow([cls.name, in_file] +
[meta.asttools.dump_python_source(base).strip()
for base in cls.bases])
def process_file(file_path):
root = ast.parse(open(file_path, 'r').read(), file_path)
for cls in find_classes(root, file_path):
yield cls
def process_directory(dir_path):
for entry in os.listdir(dir_path):
for cls in process_file_or_directory(os.path.join(dir_path, entry)):
yield cls
def process_file_or_directory(file_or_directory):
if os.path.isdir(file_or_directory):
return process_directory(file_or_directory)
elif file_or_directory.endswith('.py'):
return process_file(file_or_directory)
else:
return []
if __name__ == '__main__':
classes = process_file_or_directory(sys.argv[1])
print_classes(classes, sys.stdout)
#class-finder.py
导入ast
导入csv
导入元
导入操作系统
导入系统
def find_类(节点,在_文件中):
如果isinstance(节点,ast.ClassDef):
产量(节点,在_文件中)
如果hasattr(节点“主体”):
对于node.body中的子节点:
#Python3.x中的'yield from find_classes(child)`
对于find_类中的x(子类,在_文件中):产生x
def打印_类(类,输出):
writer=csv.writer(输出)
对于cls,类中的in_文件:
writer.writerow([cls.name,在_文件中]+
[meta.asttools.dump_python_source(base.strip())
对于cls.bases中的base])
def进程文件(文件路径):
root=ast.parse(打开(文件路径,'r').read(),文件路径)
对于find_类(根目录、文件路径)中的cls:
收益率
def进程目录(目录路径):
对于os.listdir(目录路径)中的条目:
对于进程\文件\或\目录(os.path.join(dir\u path,entry))中的cls:
收益率
def进程文件或目录(文件或目录):
如果os.path.isdir(文件或目录):
返回进程目录(文件目录)
elif文件或目录.endswith('.py'):
返回进程文件(文件或目录)
其他:
返回[]
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
classes=进程文件或目录(sys.argv[1])
打印类(类,系统标准)
看看@Fredrik,我很快浏览了文档。似乎
inspect
只对实时(运行时)代码有效。我错了吗?我认为我需要使用静态分析,因为不是每个函数/类都会用于任何给定的代码运行。使用AST,也许可以作为一个示例?您实际要做的是:请记住,您可以创建,在运行时加载并实例化类,这样就没有办法让它在100%的时间内100%正确。@MartinThurau感谢您花时间提供反馈。我重新阅读了这个问题,意识到我没有很好地说明我的目标,所以我添加了一些新的信息,说明我实际上在做什么。
# class-finder.py
import ast
import csv
import meta
import os
import sys
def find_classes(node, in_file):
if isinstance(node, ast.ClassDef):
yield (node, in_file)
if hasattr(node, 'body'):
for child in node.body:
# `yield from find_classes(child)` in Python 3.x
for x in find_classes(child, in_file): yield x
def print_classes(classes, out):
writer = csv.writer(out)
for cls, in_file in classes:
writer.writerow([cls.name, in_file] +
[meta.asttools.dump_python_source(base).strip()
for base in cls.bases])
def process_file(file_path):
root = ast.parse(open(file_path, 'r').read(), file_path)
for cls in find_classes(root, file_path):
yield cls
def process_directory(dir_path):
for entry in os.listdir(dir_path):
for cls in process_file_or_directory(os.path.join(dir_path, entry)):
yield cls
def process_file_or_directory(file_or_directory):
if os.path.isdir(file_or_directory):
return process_directory(file_or_directory)
elif file_or_directory.endswith('.py'):
return process_file(file_or_directory)
else:
return []
if __name__ == '__main__':
classes = process_file_or_directory(sys.argv[1])
print_classes(classes, sys.stdout)