关于python:显示调用特定方法的行

Show lines where a particular method is called

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

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

我试着用grep打印这些行,但这对具有相同名称的方法(例如,我尝试使用名为connect()的方法,但是24个类有一个名为connect的方法…我想把这个过滤一下特殊类别(和/或模块)。


对于函数的使用,我会比较定期地进行grep。幸运的是,我从来没有对如此大量复制的东西感兴趣。

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

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

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


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

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

要将此应用于对象,您必须在输入对象时进行堆栈,然后跳转到它们的类,并且在遇到对函数的调用时,表示它是从该特定对象调用的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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


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

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

但是,您可以使用来自sys、inspect和traceback模块的一些技巧来跟踪代码执行期间发生的所有事情。

例如,您可以设置跟踪函数,该函数将在让每个解释器帧执行之前抓取它:

1
2
3
4
5
6
7
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代码匹配,可以使用标记化模块。当标记化文件出现在跟踪中时,可以缓存它们,并在需要时从相应的行中提取python代码。

使用上面提到的所有东西,你可以做一些漫游,包括编写字节代码解压器,像在C中使用goto一样在代码上跳来跳去,强制中断线程(如果您不知道自己在做什么,则不推荐),跟踪哪个函数调用了您的函数(对于流服务器来说,很好地识别客户机占用了它们的流部分)。还有各种疯狂的事情。

我不得不说的高级疯狂的东西。不要以这种方式破坏代码流,除非它是绝对必要的,而且您不知道自己在做什么。

我会投反对票的,因为我提到这样的事情是可能的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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()

现在,您可以在跟踪函数中编写它,而不是固定方法,但是非常聪明,不依赖变量名,而是依赖地址和内容,并且可以跟踪何时何地发生的事情。大工作,但可能。


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

因此,像objectn.connect()这样的东西并不关心objectn是什么(它可以是一个模块、一个类、一个获取属性的函数,…)。它也不关心connect是否是一个方法,或者它是一个可以调用的类,或者函数的工厂。它很乐意接受任何一个objectn,当您试图获得属性connect时,它以某种方式返回一个可调用的。

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

1
2
3
4
5
6
7
class Fun(object):
    def connect(self):
        return 100

objectn = Fun()

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

你不可能可靠地搜索objectn.connect()和match来匹配(lambda x: x())(getattr(objectn, '{0}t'.format('co' + {0:'nnec'}[0]))),但两者都调用objectn的方法connect

所以我很抱歉地说,即使有抽象语法树,(可选)注释和静态代码分析,它(几乎是?)找不到调用特定类的特定方法的所有位置。