4

Python コード ベースでクラスと関数の依存関係の分析を実行しようとしています。csv最初のステップは、Python のモジュールと正規表現を使用して、Excel にインポートするための .csv ファイルを作成することでした。

私が持っているものの現在のバージョンは次のようになります。

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 行以上) プロジェクトを継承したためです。それらはすべて、1 つのソース ディレクトリ内のファイルのコレクションとして存在します。

一部のソース ファイルには、接線方向に関連する数十のクラスが含まれており、1 万行以上の長さがあり、保守が困難になっています。私は、すべてのクラスを取得し、パッケージ化に関するヒッチハイク ガイドをベースとして使用して、よりまとまりのある構造にパッケージ化することの相対的な難しさについて分析を開始しています。その分析のために私が気にかけていることの一部は、クラスがそのファイル内の他のクラスとどのように絡み合っているか、および特定のクラスが依存しているインポートまたは継承されたクラスです。

4

1 に答える 1

3

私はこれを実装することから始めました。次のコードをファイルに入れて実行し、分析するファイルまたはディレクトリの名前を渡します。見つかったすべてのクラス、見つかったファイル、およびクラスのベースが出力されます。これはインテリジェントではないためFoo、コード ベースで 2 つのクラスが定義されている場合、どちらが使用されているかはわかりませんが、それが出発点です。

このコードは、pythonastモジュールを使用してファイルを調べ、すべてのノード.pyを見つけます。ClassDef次に、このmetaパッケージを使用してそれらのビットを出力します。このパッケージをインストールする必要があります。

$ pip install -e git+https://github.com/srossross/Meta.git#egg=meta

出力例、 django-featured-itemに対して実行:

$ python class-finder.py /path/to/django-featured-item/featureditem/
FeaturedField,../django-featured-item/featureditem/fields.py,models.BooleanField
SingleFeature,../django-featured-item/featureditem/tests.py,models.Model
MultipleFeature,../django-featured-item/featureditem/tests.py,models.Model
Author,../django-featured-item/featureditem/tests.py,models.Model
Book,../django-featured-item/featureditem/tests.py,models.Model
FeaturedField,../django-featured-item/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)
于 2013-03-20T10:38:21.837 に答える