显示调用特定方法的行

Show lines where a particular method is called

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

可以在 re 模块及其方法 re.findall 上显示示例。

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

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

因为像 objectn.connect() 这样的东西不关心 objectn 是什么(它可以是一个模块,一个 class,一个获取属性的函数,...) .它也不关心 connect 是一个方法还是恰好是可调用的 class 或函数工厂。当您尝试获取属性 connect 时,它会很乐意接受任何以某种方式返回可调用对象的 objectn

不仅如此,还有很多方法可以调用方法,假设是这样的:

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]))),但两者都会调用 objectn 的方法 connect

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

我经常 grep 查找函数的使用情况。幸运的是,我从来没有对如此大量重复的东西感兴趣。

如果 Class.method 的错误命中太常见而无法手动过滤,我可能会这样做,而不是编写一次性代码。首先 grep for class Class 以找到具有 class 定义的 module 并记下行的范围。然后 grep 该模块以获得 self.method 并删除或忽略该范围之外的命中。然后为 import modulefrom module 搜索所有感兴趣的模块,以查找可能使用 class 和方法的模块。然后根据导入的具体形式grep成组的模块。

正如其他人指出的那样,即使这样也会错过使用别名作为方法名称的调用。但是只有您知道这是否是您的场景的问题。据我所知,我所做的并不是这样。

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

您可以使用 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()

现在,您可以在跟踪函数而不是固定方法中编写此代码,但要巧妙地不依赖变量名,而是依赖地址和其他内容,您可以跟踪何时何地发生了什么。伟大的工作,但可能。

我将此添加为另一个答案,因为代码太大,无法在第一个答案中全部压缩。

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

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

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



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