猿问

如何使用 Python 内省查找对方法的调用?

我刚刚在一个项目中发现了一些测试方法,这些方法没有所需的“test_”前缀来确保它们实际运行。应该可以通过一些 linting 来避免这种情况:

  1. TestCase在代码库中查找所有断言调用。

  2. 在调用层次结构中查找名称以“test_”开头的方法。

  3. 如果没有这样的方法,则打印错误消息。

我想知道如何做前两个,这基本上归结为一个问题:如何在我的代码库中找到对特定方法的所有调用?

Grepping 或其他文本搜索不会做,因为我需要内省结果并找到父方法等,直到我到达测试方法或没有更多调用者为止。我需要获得对该方法的引用,以避免匹配与我正在寻找的方法同名的方法。


隔江千里
浏览 290回答 2
2回答

喵喔喔

这里有两种可能的方法。静态方法:您可以使用 ast 模块解析代码库以识别所有函数调用并一致地存储调用的来源和目标。您必须标识所有类和函数定义以跟踪每个调用的当前上下文。这里的限制是,如果您调用实例方法,则没有简单的方法来识别该方法实际属于哪个类。如果您使用引用模块的变量,则相同这是一个访问者子类,它可以读取 Python 源文件并构建一个 dict {caller: callee}:class CallMapper(ast.NodeVisitor):    def __init__(self):        self.ctx = []        self.funcs = []        self.calls = collections.defaultdict(set)    def process(self, filename):        self.ctx = [('M', os.path.basename(filename)[:-3])]        tree = ast.parse(open(filename).read(), filename)        self.visit(tree)        self.ctx.pop()    def visit_ClassDef(self, node):        print('ClassDef', node.name, node.lineno, self.ctx)        self.ctx.append(('C', node.name))        self.generic_visit(node)        self.ctx.pop()    def visit_FunctionDef(self, node):        print('FunctionDef', node.name, node.lineno, self.ctx)        self.ctx.append(('F', node.name))        self.funcs.append('.'.join([elt[1] for elt in self.ctx]))        self.generic_visit(node)        self.ctx.pop()    def visit_Call(self, node):        print('Call', vars(node.func), node.lineno, self.ctx)        try:            id = node.func.id        except AttributeError:            id = '*.' + node.func.attr        self.calls['.'.join([elt[1] for elt in self.ctx])].add(id)        self.generic_visit(node)动态方法:如果您真的想确定调用了什么方法,当多个方法可以共享相同的名称时,您将不得不使用动态方法。您可以装饰类中的单个函数或所有方法,以计算它们被调用的次数,以及它们的调用位置。然后您将开始测试并检查实际发生的情况。这是一个函数,它将装饰类中的所有方法,以便所有调用的数字都将存储在字典中:def tracemethods(cls, track):    def tracker(func, track):        def inner(*args, **kwargs):            if func.__qualname__ in track:                track[func.__qualname__] += 1            else:                track[func.__qualname__] = 1            return func(*args, *kwargs)        inner.__doc__ = func.__doc__        inner.__signature__ = inspect.signature(func)        return inner    for name, func in inspect.getmembers(cls, inspect.isfunction):        setattr(cls, name, tracker(func, track))您可以调整该代码以浏览解释器堆栈以识别每个调用的调用者,但这不是很容易,因为您获得调用者函数的非限定名称,并且必须使用文件名和行号来唯一标识调用者.

胡子哥哥

好吧,这是一个开始。您将使用几个标准库:import disimport inspect假设您对此源代码感兴趣:myfolder/myfile.py 然后执行以下操作:import myfolder.myfiledef some_func():    ''loads = {'LOAD_GLOBAL', 'LOAD_ATTR'}name_to_member = dict(inspect.getmembers(myfolder.myfile))for name, member in name_to_member.items():    if type(member) == type(some_func):        print(name)        for ins in dis.get_instructions(member):            if ins.opname in loads:                print(name, ins.opname, ins.argval)其他有趣的事情:运行dis.dis(member),或打印出来dis.code_info(member)。这将让您访问文件中定义的每个函数,并访问每个可执行语句以查看它是否可能是您关心的方法调用。然后由您来使用潜在的测试方法做正确的事情。
随时随地看视频慕课网APP

相关分类

Python
我要回答