显示调用特定方法的行

时间:2017-05-01 22:07:05

标签: python parsing abstract-syntax-tree introspection

假设您有特定模块的特定方法(函数) (特定类别,可选)。是否可以通过内省库源代码打印调用(使用)该方法的所有行? 它可以在内部调用(使用self.method_name())或在外部调用 (源文件1中的object1.method_name(),源中的object2.method_name() 源文件N)中的文件2,...和objectN.method_name()。

可以在re模块上显示一个示例,它的方法为re.findall

我尝试用grep打印这些行,但这是一个问题 具有相同名称的方法(例如,我尝试使用名为connect()的方法, 但是24个类有一个名为connect的方法...我想过滤这个 特定的类(和/或模块)。

4 个答案:

答案 0 :(得分:3)

我非常经常地使用函数。幸运的是,我从未对那些会被重复的东西感兴趣。

如果Class.method的错误点击太频繁而无法手动过滤,那么我可能会做的不是编写一次性代码。 class Class的第一个grep,用类定义查找module并记下行的范围。然后grep该模块self.method并删除或忽略该范围之外的命中。然后grep import modulefrom module感兴趣的所有模块,以查找可能使用该类和方法的模块。然后根据具体的导入形式grep模块组。

正如其他人所指出的那样,即使这样也会错过使用别名作为方法名称的调用。但只有您可以知道这是否适用于您的方案。我所知道的不是我所做的。

完全不同的方法,不依赖于名称,是在使用动态内省确定调用者之后,使用记录其调用的代码来检测函数。 (我相信还有SO Q&关于这一点。)

答案 1 :(得分:1)

你可能知道,但我不能冒你不知道的风险:Python不是强类型语言。

objectn.connect()之类的东西并不关心objectn是什么(它可能是一个模块,一个类,一个获取属性的函数,......)。它也不关心connect是一个方法,还是一个碰巧可以调用的类或函数的工厂。当你试图获得属性objectn时,它会愉快地接受以某种方式回复可调用的任何connect

不仅如此,有很多方法可以调用方法,只需要假设:

class Fun(object):
    def connect(self):
        return 100

objectn = Fun()

(lambda x: x())(getattr(objectn, '{0}t'.format('co' + {0:'nnec'}[0])))

您无法可靠地搜索objectn.connect()并且匹配获得(lambda x: x())(getattr(objectn, '{0}t'.format('co' + {0:'nnec'}[0])))的匹配,但两者都会调用connect的方法objectn

所以我很遗憾地说,即使使用抽象语法树,(可选)注释和静态代码分析,也几乎不可能找到调用特定类的特定方法的所有地方。 / p>

答案 2 :(得分:1)

您可以使用ast或编译器模块来挖掘已编译的代码,并找出显式调用函数的位置。

您也可以使用带有ast标志的compile()编译代码,并将其解析为抽象语法树。然后你去看看里面叫什么。

但是你可以使用sys,inspect和traceback模块中的一些技巧来追踪代码执行过程中发生的所有事情。

例如,您可以设置跟踪功能,在执行每个解释器框架之前抓取它们:

import dis
import sys
def tracefunc (frame, evt, arg):
    print frame.f_code.co_filename, frame.f_lineno, evt
    print frame.f_code.co_name, frame.f_code.co_firstlineno
    #print dis.dis(f.f_code)
sys.settrace(tracefunc)

在此代码之后,每个完成的步骤都将打印出包含代码,步骤行,代码对象开始的文件的文件,它将对其进行反汇编,以便您可以看到所有正在完成的工作或将在后台完成(如果你取消注释)。

如果要将执行的字节码与Python代码匹配,可以使用tokenize模块。 您可以在跟踪中显示标记化文件的缓存,并在需要时从相应的行中抓取Python代码。

使用所有提到的东西,你可以做漫游,包括编写字节码反编译器,跳过你的代码,就像在C中使用goto, 强行中断线程(如果你不确切知道你的内容,不推荐),跟踪哪个函数称为你的函数(很好的流服务器识别客户端捕获它们的部分流), 和各种疯狂的东西。

我不得不说高级疯狂的东西。 不要以这样的方式处理代码流,除非它是绝对必要的,你不完全知道你在做什么。

我会因为提到这些事情甚至可能而被投票。

动态检测client()尝试获取内容的示例:

from thread import get_ident
import sys

class Distributer:
    def read (self):
        # Who called me:
        cf = sys._current_frames()
        tid = get_ident() # Make it thread safe
        frame = cf[tid]
        # Now, I was called in one frame back so
        # go back and find the 'self' variable of a method that called me
        # and self, of course, contains the instance from which I was called
        client = frame.f_back.f_locals["self"]
        print "I was called by", client

class Client:
    def __init__ (self, name):
        self.name = name

    def snatch (self):
        # Now client gets his content:
        content.read()

    def __str__ (self):
        return self.name

content = Distributer()
clients = [Client("First"), Client("Second"), Client("Third"), Client("Fourth"), Client("Etc...")]
for client in clients:
    client.snatch()

现在,您在跟踪函数中编写此代码而不是固定方法,但是巧妙地,不依赖于变量名称而是依赖于地址和内容,您可以跟踪在何时何地发生的事情。很大的工作,但可能。

答案 3 :(得分:1)

我将此作为另一个答案添加,因为代码太大而无法在第一个代码中将所有内容组合在一起。

这是一个非常简单的例子,用于找出使用抽象语法树调用哪个函数。

要在输入它们时必须堆叠的对象上应用它,然后跳转到它们的类,并在遇到对函数的调用时说它是从该特定对象调用的。

您可以看到模块涉及时会变得多么复杂。 应输入每个模块,并映射其子模块和所有功能,以便您可以跟踪对它们的调用等。



import ast

def walk (node):
    """ast.walk() skips the order, just walks, so tracing is not possible with it."""
    end = []
    end.append(node)
    for n in ast.iter_child_nodes(node):
        # Consider it a leaf:
        if isinstance(n, ast.Call):
            end.append(n)
            continue
        end += walk(n)
    return end

def calls (tree):
    """Prints out exactly where are the calls and what functions are called."""
    tree = walk(tree) # Arrange it into our list
    # First get all functions in our code:
    functions = {}
    for node in tree:
        if isinstance(node, (ast.FunctionDef, ast.Lambda)):
            functions[node.name] = node
    # Find where are all called functions:
    stack = []
    for node in tree:
        if isinstance(node, (ast.FunctionDef, ast.Lambda)):
            # Entering function
            stack.append(node)
        elif stack and hasattr(node, "col_offset"):
            if node.col_offset<=stack[-1].col_offset:
                # Exit the function
                stack.pop()
        if isinstance(node, ast.Call):
            if isinstance(node.func, ast.Attribute):
                fname = node.func.value.id+"."+node.func.attr+"()"
            else: fname = node.func.id+"()"
            try:
                ln = functions[fname[:-2]].lineno
                ln = "at line %i" % ln
            except: ln = ""
            print "Line", node.lineno, "--> Call to", fname, ln
            if stack:
                print "from within", stack[-1].name+"()", "that starts on line", stack[-1].lineno
            else:
                print "directly from root"

code = """
import os

def f1 ():
    print "I am function 1"
    return "This is for function 2"

def f2 ():
    print f1()
    def f3 ():
        print "I am a function inside a function!"
    f3()
f2()
print "My PID:", os.getpid()
"""

tree = ast.parse(code)

calls(tree)

The output is:

Line 9 --> Call to f1() at line 4
from within f2() that starts on line 8
Line 12 --> Call to f3() at line 10
from within f2() that starts on line 8
Line 13 --> Call to f2() at line 8
directly from root
Line 14 --> Call to os.getpid()
directly from root