在大型python项目中查找无效代码[关闭]


73

我已经看到了如何在Python代码中找到未使用的函数?但这确实很老,并不能真正回答我的问题。

我有一个大型python项目,其中包含多个由多个入口点脚本共享的库。这个项目已经吸引了许多作者多年,所以这里有很多无效代码。你知道该怎么做。

我知道找到所有无效代码是不确定的。我需要的是一个可以找到所有未在任何地方调用的功能的工具。我们不会根据函数名称的字符串来调用函数,因此我不会担心任何病理性...

我刚刚安装了pylint,但它似乎是基于文件的,并没有非常注意文件间的依赖关系,甚至功能的依赖关系。

显然,我可以在所有文件中对def进行grep运算,从中获取所有函数名称,并对每个函数名称进行grep运算。我只是希望已经有了一些比这还聪明的东西。

预计到达时间:请注意,我不期望或想要完美的东西。我和其他任何人一样都知道我的防止停顿问题(当我查看递归可枚举的东西时,我真的没有教过我知道的计算理论)。任何试图通过实际运行代码来近似它的事情都将花费太长时间。我只希望在语法上经过一段代码后说:“肯定使用了此功能。可以使用此功能,而绝对不使用此功能,甚至没有人知道它的存在!” 而且前两个类别并不重要。


已经有一个开放了2.5年。我认为主要是因为他们知道在最困难的情况下这在数学上是不可能的。
Brian Postow'3

听起来好像对您来说足够了。也许使用parser会给您更好的控制。
彼得·伍德

Answers:


43

您可能要尝试秃鹰。由于Python的动态特性,它无法捕获所有内容,但可以捕获很多内容,而无需像coverage.py之类的完整测试套件,其他工具也需要工作。


1
这是完美的,很高兴最终给出了真正的答案。秃鹰不保守的死代码分析,原来提问者一直在寻找
deontologician

秃鹰似乎很棒,但是它实际上并不能与django一起使用...而且不幸的是,django-coverage插件太旧了,它需要长期存在的依赖关系。:(
szeitlin

16

尝试运行Ned Batcheldercoverage.py

Coverage.py是用于测量Python程序的代码覆盖率的工具。它监视您的程序,并指出代码的哪些部分已执行,然后分析源代码以识别可能已执行但尚未执行的代码。


5
那将涉及在所有可能的配置中运行代码。我不想这样做。我不想要所有无效代码的完整列表。我只想要一个快速而肮脏的近似值。留下一些无效的代码就可以了。
Brian Postow'3

4
“留下一些无效的代码就可以了”-我真的不认为这永远都可以。
madCode

我认为,留下无效的代码不是很好,但是很难实现。
帕特里克·巴苏

9

即使代码不做任何花哨的事情,也很难确定不执行代码就调用哪些函数和方法。普通函数调用很容易检测,但是方法调用确实很难。只是一个简单的例子:

class A(object):
    def f(self):
        pass

class B(A):
    def f(self):
        pass

a = []
a.append(A())
a.append(B())
a[1].f()

没什么特别的事情在这里,但任何脚本试图以确定是否A.f()B.f()称为将有一个相当艰难的时间,而无需实际执行代码这样做。

尽管上面的代码没有做任何有用的事情,但是它肯定使用了真实代码中出现的模式-将实例放入容器中。实际的代码通常会做更复杂的事情-酸洗和酸洗,分层数据结构,条件。

如前所述,仅检测表单的普通函数调用

function(...)

要么

module.function(...)

会很容易。您可以使用该ast模块来解析您的源文件。您将需要记录所有导入,以及用于导入其他模块的名称。您还需要跟踪顶级函数定义以及这些函数内部的调用。这将为您提供一个依赖关系图,并且您可以使用NetworkX来检测该图的连接组件。

虽然这听起来可能很复杂,但是用不到100行代码就可以完成。不幸的是,几乎所有主要的Python项目都使用类和方法,因此它几乎没有帮助。


1
讨论类和异构列表是一个很好的方法。我认为目前,我只想说“函数f被使用了”,所以我假设两者都是必要的。而且,真的,如果我有A和B都具有函数F,并且它们不在同一个异构列表中,或者可以互换使用,那么我在函数命名方面就遇到了问题……
Brian Postow 2012年

1
不知道我是否真的将其称为函数命名问题。为了让标准库中的例子:dict.get()queue.Queue.get()并且pickle.Pickler.get()是完全无关的。以某种方式命名空间的全部目的是允许对不同的事物使用相同的名称。
Sven Marnach 2012年

好,可以。我想我假设具有诸如get,set,equals,init等标准名称的东西都将在某个地方使用,所以我并不真正担心那些东西。但是,是的,您是正确的。
Brian Postow'3

6

这是我至少暂时使用的解决方案:

grep 'def ' *.py > defs
# ...
# edit defs so that it just contains the function names
# ...
for f in `cat defs` do
    cat $f >> defCounts
    cat *.py | grep -c $f >> defCounts
    echo >> defCounts
done

然后,我看一下引用很少的单个函数(<3说)

这很丑陋,它只能给我大概的答案,但我认为这足以作为开始。你们都有什么想法?


我应该提到for循环是bash语法。
Brian Postow'3

注意:由于许多原因,此代码完全无法使用..并非最不重要的是,它存在语法错误,并且为错误的内容而抓紧(应该只是函数名,等等)...不要运行它。但它仍然是一个不错的主意......简单的字符串计数等
埃里克Aronesty

我说的只是大概的....
Brian Postow


4

在下面的行中,您可以列出所有显然不用作属性,函数调用,修饰符或返回值的函数定义。因此,这正是您要寻找的。它不是完美的,它很慢,但是我从来没有收到任何误报。(对于linux,您必须替换ackack-grep

for f in $(ack --python --ignore-dir tests -h --noheading "def ([^_][^(]*).*\):\s*$" --output '$1' | sort| uniq); do c=$(ack --python -ch "^\s*(|[^#].*)(@|return\s+|\S*\.|.*=\s*|)"'(?<!def\s)'"$f\b"); [ $c == 0 ] && (echo -n "$f: "; ack --python --noheading "$f\b"); done

您可以通过用ack代替ack来提高速度grep --include "*.py"
乔纳森·哈特利

1

如果您的代码包含大量测试(这非常有用),请使用代码覆盖插件运行它们,然后即可看到未使用的代码。)


8
除非您可能要测试无效代码。即,它在系统的其余部分都没有使用
John La Rooy

我在代码中很少进行测试。在确定了实际需要测试的内容之后,我应该做的事情就是添加单元测试。如果我测试所有无效代码,我将有效地使其恢复活力,这不是我要做的想。
Brian Postow'3

1

可以通过一个简单的pylint插件很快实现IMO:

  • 记住S1集中的每个分析函数/方法(/类?)
  • 跟踪S2集中的每个调用的函数/方法(/类?)
  • 在报告中显示S1-S2

然后,您将必须在所有代码库上调用pylint以获得有意义的东西。当然,正如所说的那样,这可能需要检查,因为可能存在推理失败或可能导致误报的情况。无论如何,这可能会大大减少要完成的grep数量。

我自己没有多少时间来做,但是任何人都可以在python-projects@logilab.org邮件列表中找到帮助。


FWIW我过去曾经写过pylint插件(不是这样做,而是做其他事情),虽然不琐碎(您在检查的代码的AST上写断言),但是它们比我预期的要容易得多。
乔纳森·哈特利
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.