Py学习  »  Python

在Python中从源文件和行号查找定义

Python程序员 • 4 年前 • 391 次点击  


我在Datadog的工作让我一直忙于应对新的和有问题的挑战。最近我偶然发现了一个问题,听起来很简单,但比我想象的要难。


问题是这样的:假设有一个文件名和一个行号,您能说出这行代码属于哪个函数、方法或类吗?


我开始深入研究标准库,但没有找到解决这个问题的任何东西。听起来好像我必须自己编写一些东西来解决这个问题。


第一步听起来很容易。打开一个文件,读取它,找到行号。就是这样。


那么,您如何知道这一行是在哪个函数中呢?您是无法预期的,除非您解析整个文件并跟踪函数定义。那使用一个正则表达式来解析每一行可能是一个解决方案呢?


您必须小心,因为函数定义可以跨越多行。


使用AST


我认为一个好的健壮的策略不是使用手动解析或类似的方法,而是直接使用Python抽象语法树(AST)。通过利用Python自己的解析代码,我确信在解析Python源文件时不会失败。

这可以通过以下代码来简单地实现:


这样您就完成了。是吗?并不是,因为这只适用于99.99%的情况。如果您的源文件使用的编码现在是ASCII或UTF-8,则该函数会失败。我知道您认为我疯了,但我希望我的代码是健壮的。


原来Python有一个cookie来以# encoding: utf-8的形式指定编码方式,正如PEP 263中定义的那样。阅读这个cookie将有助于找到其编码方式。


要做到这一点,我们需要以二进制模式打开文件,使用一个正则表达式来匹配数据,并且……嗯,它很乏味,而且已经有人为我们实现了它,所以让我们来使用Python所提供的神奇的tokenize.open函数:


这应该在100%的时间内都有效。直到被证明并非如此。


浏览AST


parse_file函数现在会返回一个Python AST。如果您从未使用过Python AST,那么它是一个巨大的树,表示您的源代码在被编译成Python字节码之前的样子。


在这个树中,应该有语句和表达式。在本例中,我们所感兴趣的是找到与我们的行号最接近的函数定义。下面是该函数的实现:  


这个函数会遍历AST的所有节点,并返回行号与我们的定义最接近的节点。如果我们有一个文件包含以下代码:


下面是以第一行到第五行为参数,调用函数filename_and_lineno_to_def得到的5个结果:


以第一行到第五行为参数,调用函数filename_and_lineno_to_def

很有用!


闭包?


前面描述的简单方法可能适用于90%的代码,但也有一些边缘情况。例如,当定义函数闭包时,上面的算法就不行了。当有如下代码时:


以第1行到第7行为参数,调用函数filename_and_lineno_to_def:

返回第1行到第7行的filename_and_lineo_to_def的结果

哎呀。显然,第6行和第7行不属于foo函数。我们的方法太简单了,以至于从第6行开始,我们就回到了y方法。


区间树


正确处理上述问题的方法是将每个函数定义视为一个区间:

可以视为区间的代码片段


无论我们请求的行号是什么,我们都应该返回负责该行所在的最小间隔的节点。


在这种情况下,我们需要一个正确的数据结构来解决我们的问题:一个区间树正好非常适合我们的情况。它允许我们快速搜索与我们的行号相匹配的代码片段。


要解决我们的问题,我们需要以下几个东西:

  • 一种计算函数的起始行号和结束行号的方法。

  • 一个用我们之前计算的间隔来填充的树。

  • 当一个行是多个函数(闭包)的一部分时,选择最佳匹配区间的方法。


计算函数区间


一个函数的区间是组成该函数主体的第一行和最后一行。通过遍历函数AST节点很容易找到这些行:

对于任意AST节点,该函数会返回一个此节点的第一个和最后一个行号的元组。


构建区间树


我们将使用intervaltree库而不是自己实现一个区间树。我们需要创建一个树,并用计算出的区间来填充它: 

就是这样:该函数会解析作为参数传入的Python文件,并将其转换为它的AST表示。然后遍历它并为区间树提供每个类和函数定义。


查询区间树


现在已经构建了区间树,我们就可以使用行号来对它进行查询了。这很简单:

如果有多个区间包含我们的行号,则该构建树可能会返回几个匹配项。在这种情况下,我们选择最小的区间并返回此节点的名称——也就是我们的类名或函数名!


任务完成


我们做到了!我们从一个简单的方法开始,并迭代到一个最终的解决方案,它覆盖了我们100%的情况。选择正确的数据结构,这里是区间树,帮助我们用一个智能的方法解决了这个问题。


>>> 今日的签到口令:z23p <<<


英文原文:https://julien.danjou.info/finding-definitions-from-a-source-file-and-a-line-number-in-python/ 
译者:Nothing
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/50219
 
391 次点击