为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

隔江千里 2021-12-17 14:55:55
我刚刚在一个项目中发现了一些测试方法,这些方法没有所需的“test_”前缀来确保它们实际运行。应该可以通过一些 linting 来避免这种情况:TestCase在代码库中查找所有断言调用。在调用层次结构中查找名称以“test_”开头的方法。如果没有这样的方法,则打印错误消息。我想知道如何做前两个,这基本上归结为一个问题:如何在我的代码库中找到对特定方法的所有调用?Grepping 或其他文本搜索不会做,因为我需要内省结果并找到父方法等,直到我到达测试方法或没有更多调用者为止。我需要获得对该方法的引用,以避免匹配与我正在寻找的方法同名的方法。
查看完整描述

2 回答

?
喵喔喔

TA贡献1735条经验 获得超5个赞

这里有两种可能的方法。


静态方法:


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

您可以调整该代码以浏览解释器堆栈以识别每个调用的调用者,但这不是很容易,因为您获得调用者函数的非限定名称,并且必须使用文件名和行号来唯一标识调用者.


查看完整回答
反对 回复 2021-12-17
?
胡子哥哥

TA贡献1825条经验 获得超6个赞

好吧,这是一个开始。您将使用几个标准库:


import dis

import inspect

假设您对此源代码感兴趣:myfolder/myfile.py 然后执行以下操作:


import myfolder.myfile


def 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)。


这将让您访问文件中定义的每个函数,并访问每个可执行语句以查看它是否可能是您关心的方法调用。然后由您来使用潜在的测试方法做正确的事情。


查看完整回答
反对 回复 2021-12-17
  • 2 回答
  • 0 关注
  • 289 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号