Как получить область видимости класса с точки зрения начальной строки и конечной строки в Python? - PullRequest
3 голосов
/ 28 сентября 2019

Я хочу проверить, использует ли класс c определенный модуль m.Я могу получить две вещи: - использование модуля в этом файле (f), - номер начальной строки классов в файле f.

Однако, чтобы узнать, использует ли конкретный класс модульМне нужно знать начальную и конечную строку класса c.Я не знаю, как получить конечную строку класса c.

Я попытался просмотреть документацию по ast, но не смог найти какой-либо метод определения области видимости класса.

Myтекущий код:

source_code = "car"
source_code_data = pyclbr.readmodule(source_code)

В переменной source_code_data это дает следующее: source_code_data value in debugger mode 1

Как вы можете видеть на рисунке, есть бельено нет конца строки, чтобы обозначить конечную строку класса.

Я ожидаю получить конечную строку класса, чтобы узнать его область действия и, наконец, узнать об использовании модуля.В настоящее время модуль имеет следующую структуру:

Module: vehicles used in file: /Users/aviralsrivastava/dev/generate_uml/inheritance_and_dependencies/car.py at: [('vehicles.Vehicle', 'Vehicle', 5), ('vehicles.Vehicle', 'Vehicle', 20)]

Итак, с этой информацией я смогу узнать, используется ли модуль в диапазоне: используется в классе.

Myвся база кода:

import ast
import os
import pyclbr
import subprocess
import sys
from operator import itemgetter

from dependency_collector import ModuleUseCollector
from plot_uml_in_excel import WriteInExcel


class GenerateUML:
    def __init__(self):
        self.class_dict = {}  # it will have a class:children mapping.

    def show_class(self, name, class_data):
        print(class_data)
        self.class_dict[name] = []
        self.show_super_classes(name, class_data)

    def show_methods(self, class_name, class_data):
        methods = []
        for name, lineno in sorted(class_data.methods.items(),
                                   key=itemgetter(1)):
            # print('  Method: {0} [{1}]'.format(name, lineno))
            methods.append(name)
        return methods

    def show_super_classes(self, name, class_data):
        super_class_names = []
        for super_class in class_data.super:
            if super_class == 'object':
                continue
            if isinstance(super_class, str):
                super_class_names.append(super_class)
            else:
                super_class_names.append(super_class.name)
        for super_class_name in super_class_names:
            if self.class_dict.get(super_class_name, None):
                self.class_dict[super_class_name].append(name)
            else:
                self.class_dict[super_class_name] = [name]
        # adding all parents for a class in one place for later usage: children
        return super_class_names

    def get_children(self, name):
        if self.class_dict.get(name, None):
            return self.class_dict[name]
        return []


source_code = "car"
source_code_data = pyclbr.readmodule(source_code)
print(source_code_data)
generate_uml = GenerateUML()
for name, class_data in sorted(source_code_data.items(), key=lambda x: x[1].lineno):
    print(
        "Class: {}, Methods: {}, Parent(s): {}, File: {}".format(
            name,
            generate_uml.show_methods(
                name, class_data
            ),
            generate_uml.show_super_classes(name, class_data),
            class_data.file
        )
    )
print('-----------------------------------------')
# create a list with all the data
# the frame of the list is: [{}, {}, {},....] where each dict is: {"name": <>, "methods": [], "children": []}
agg_data = []
files = {}
for name, class_data in sorted(source_code_data.items(), key=lambda x: x[1].lineno):
    methods = generate_uml.show_methods(name, class_data)
    children = generate_uml.get_children(name)
    # print(
    #     "Class: {}, Methods: {}, Child(ren): {}, File: {}".format(
    #         name,
    #         methods,
    #         children,
    #         class_data.file
    #     )
    # )
    agg_data.append(
        {
            "Class": name,
            "Methods": methods,
            "Children": children,
            "File": class_data.file
        }
    )
    files[class_data.file] = name
print('-----------------------------------------')
# print(agg_data)


for data_index in range(len(agg_data)):
    agg_data[data_index]['Dependents'] = None
    module = agg_data[data_index]["File"].split('/')[-1].split('.py')[0]
    used_in = []
    for file_ in files.keys():
        if file_ == agg_data[data_index]["File"]:
            continue
        collector = ModuleUseCollector(module)
        source = open(file_).read()
        collector.visit(ast.parse(source))
        print('Module: {} used in file: {} at: {}'.format(
            module, file_, collector.used_at))
        if len(collector.used_at):
            used_in.append(files[file_])
    agg_data[data_index]['Dependents'] = used_in
'''
# checking the dependencies
dependencies = []
for data_index in range(len(agg_data)):
    collector = ModuleUseCollector(
        agg_data[data_index]['File'].split('/')[-1].split('.py')[0]
    )
    collector.visit(ast.parse(source))
    dependencies.append(collector.used_at)

agg_data[source_code_index]['Dependencies'] = dependencies
# next thing, for each class, find the dependency in each class.

'''
print('------------------------------------------------------------------')
print('FINAL')
for data in agg_data:
    print(data)
    print('-----------')
    print('\n')

# The whole data is now collected and we need to form the dataframe of it:
'''
write_in_excel = WriteInExcel(file_name='dependency_1.xlsx')
df = write_in_excel.create_pandas_dataframe(agg_data)
write_in_excel.write_df_to_excel(df, 'class_to_child_and_dependents')
'''
'''
print(generate_uml.class_dict)
write_in_excel = WriteInExcel(
    classes=generate_uml.class_dict, file_name='{}.xlsx'.format(source_code))
write_in_excel.form_uml_sheet_for_classes()
'''

1 Ответ

1 голос
/ 28 сентября 2019

Редактировать: инвазивный метод

Мне удалось найти способ сделать это с помощью pyclbr, но это связано с изменением частей исходного кода.По сути, вы делаете стек (который обычно является списком) пользовательским классом.Когда элемент удаляется из стека (когда заканчивается его область действия), добавляется номер конечной строки.Я попытался сделать его как можно более инвазивным.

Сначала определите класс стека в модуле pyclbr:

class Stack(list):

    def __init__(self):
        import inspect
        super().__init__()

    def __delitem__(self, key):
        frames = inspect.stack()
        setattr(self[key][0], 'endline', frames[1].frame.f_locals['start'][0] - 1)
        super().__delitem__(key)

Затем измените стек в функции _create_tree, котораяизначально находится на линии 193:

stack = Stack()

Теперь вы также можете получить доступ к конечной строке, используя

class_data.endline



Мне не удалось найтирешение, которое использует библиотеки, которые вы используете.Поэтому я написал простой синтаксический анализатор, который находит начальную и конечную строки каждого класса, содержащегося в модуле.Вы должны передать либо текстовую оболочку, либо список, созданный readlines() в вашем файле.Также стоит отметить, что если класс продолжается до конца файла, то конец равен -1.

import re

def collect_classes(source):
    classes = {}
    current_class = None
    for lineno, line in enumerate(source, start=1):
        if current_class and not line.startswith(' '):
            if line != '\n':
                classes[current_class]['end'] = lineno - 1
                current_class = None

        if line.startswith('class'):
                current_class = re.search(r'class (.+?)(?:\(|:)', line).group(1)
                classes[current_class] = {'start': lineno}

    if current_class:
        classes[current_class]['end'] = -1

    return classes

import datetime
file = open(datetime.__file__, 'r') # opening the source of datetime for a test
scopes = collect_classes(file)
print(scopes)

Это приводит к:

{'timedelta': {'start': 454, 'end': 768}, 'date': {'start': 774, 'end': 1084}, 'tzinfo': {'start': 1092, 'end': 1159}, 'time': {'start': 1162, 'end': 1502}, 'datetime': {'start': 1509, 'end': 2119}, 'timezone': {'start': 2136, 'end': 2251}}

...