Как найти все варианты использования функции или переменной Python в пакете Python

Я пытаюсь наметить использование/причины функций и переменных в пакете python на функциональном уровне. Есть несколько модулей, в которых функции/переменные используются в других функциях, и я хотел бы создать словарь, который выглядит примерно так:

{'function_name':{'uses': [...functions used in this function...],
                  'causes': [...functions that use this function...]},
 ...
}

Функции, о которых я говорю, должны быть определены в модулях пакета.

Как бы я начал с этого? Я знаю, что могу выполнить итерацию по пакету __dict__ и протестировать функции, определенные в пакете, выполнив следующие действия:

import package

import inspect
import types

for name, obj in vars(package).items():
    if isinstance(obj, types.FunctionType):
        module, *_ = inspect.getmodule(obj).__name__.split('.')
        if module == package.__name__:
            # Now that function is obtained need to find usages or functions used within it

Но после этого мне нужно найти функции, используемые в текущей функции. Как это может быть сделано? Есть ли что-то уже разработанное для такого рода работ? Я думаю, что библиотекам профилирования, возможно, придется сделать что-то подобное.


person pbreach    schedule 22.12.2016    source источник
comment
проверьте этот docs.python.org/2/library/ast.html   -  person kmaork    schedule 22.12.2016
comment
Пока выглядит многообещающе. Очевидно, это недостающие ast документы: greentreesnakes.readthedocs.io/en/latest/index. html   -  person pbreach    schedule 22.12.2016
comment
@Ни. Спасибо за предложение этого модуля. В итоге я реализовал то, что действительно работало.   -  person pbreach    schedule 22.12.2016


Ответы (1)


Модуль ast, предложенный в комментариях, в итоге заработал хорошо. Вот созданный мной класс, который используется для извлечения функций или переменных, определенных в пакете, которые используются в каждой функции.

import ast
import types
import inspect


class CausalBuilder(ast.NodeVisitor):

    def __init__(self, package):
        self.forest = []
        self.fnames = []

        for name, obj in vars(package).items():
            if isinstance(obj, types.ModuleType):
                with open(obj.__file__) as f:
                    text = f.read()
                tree = ast.parse(text)
                self.forest.append(tree)
            elif isinstance(obj, types.FunctionType):
                mod, *_ = inspect.getmodule(obj).__name__.split('.')
                if mod == package.__name__:
                    self.fnames.append(name)

        self.causes = {n: [] for n in self.fnames}

    def build(self):
        for tree in self.forest:
            self.visit(tree)
        return self.causes

    def visit_FunctionDef(self, node):
        self.generic_visit(node)
        for b in node.body:
            if node.name in self.fnames:
                self.causes[node.name] += self.extract_cause(b)

    def extract_cause(self, node):
        nodes = [node]
        cause = []
        while nodes:
            for i, n in enumerate(nodes):
                ntype = type(n)
                if ntype == ast.Name:
                    if n.id in self.fnames:
                        cause.append(n.id)
                elif ntype in (ast.Assign, ast.AugAssign, ast.Attribute,
                               ast.Subscript, ast.Return):
                    nodes.append(n.value)
                elif ntype in (ast.If, ast.IfExp):
                    nodes.append(n.test)
                    nodes.extend(n.body)
                    nodes.extend(n.orelse)
                elif ntype == ast.Compare:
                    nodes.append(n.left)
                    nodes.extend(n.comparators)
                elif ntype == ast.Call:
                    nodes.append(n.func)
                elif ntype == ast.BinOp:
                    nodes.append(n.left)
                    nodes.append(n.right)
                elif ntype == ast.UnaryOp:
                    nodes.append(n.operand)
                elif ntype == ast.BoolOp:
                    nodes.extend(n.values)
                elif ntype == ast.Num:
                    pass
                else:
                    raise TypeError("Node type `{}` not accounted for."
                                    .format(ntype))

                nodes.pop(nodes.index(n))

        return cause

Класс можно использовать, сначала импортировав пакет python и передав его конструктору, а затем вызвав метод build следующим образом:

import package

cb = CausalBuilder(package)
print(cb.build())

Который распечатает словарь, содержащий набор ключей, представляющих имя функции, и значения, которые представляют собой списки, указывающие функции и / или переменные, которые используются в функции. Учитывается не каждый тип ast, но в моем случае этого было достаточно.

Реализация рекурсивно разбивает узлы на более простые типы, пока не достигнет ast.Name, после чего она может извлечь имя переменной, функции или метода, которые используются в целевой функции.

person pbreach    schedule 22.12.2016
comment
На данный момент принимаю мой собственный ответ, поскольку он решил мою проблему с использованием модулей ast, предложенных в комментариях. Любые другие ответы или советы приветствуются. - person pbreach; 02.01.2017