2016-12-22 11 views
6

Я пытаюсь определить использование/причины функций и переменных в пакете 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 

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

+1

проверить это https://docs.python.org/2/library/ast.html –

+0

Выглядит многообещающе. По-видимому, это «отсутствующие» документы 'ast': https://greentreesnakes.readthedocs.io/en/latest/index.html – pbreach

+0

@Ni. Спасибо за предложение этого модуля. Я закончил тем, что на самом деле работал. – pbreach

ответ

0

Модуль 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 

Класс может быть использован первым импортируя пакет питона и переходя к конструктору, затем вызывая build метод, как так:

import package 

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

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

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

+0

Принимая мой собственный ответ на данный момент, поскольку он решил мою проблему, используя модули 'ast', предложенные в комментариях. Любые другие ответы или советы приветствуются. – pbreach