在大型python项目中查找死代码

时间:2012-03-01 22:03:07

标签: python pylint code-cleanup

我见过How can you find unused functions in Python code?,但那真的很旧,并没有真正回答我的问题。

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

我知道找到所有死代码是不可判定的。我所需要的只是一个工具,可以找到所有未被调用的函数。我们没有根据函数名称的字符串调用函数做任何事情,所以我不担心任何病态......

我刚安装了pylint,但它似乎是基于文件的,并没有太多关注文件间依赖关系,甚至是函数依赖关系。

显然,我可以在所有文件中查找def,从中获取所有函数名称,并为每个函数名称执行grep。我只是希望有一些比那里更聪明的东西。

ETA:请注意,我不希望或想要完美的东西。我知道我的停止问题证明就像任何人一样(当我看到递归可枚举的东西时,我真的没有教过计算理论)。任何试图通过实际运行代码来逼近它的东西都会花费太长时间。我只是想要在语法上通过代码的东西并说“这个函数是肯定使用的。可以使用这个函数,这个函数绝对不使用,其他人甚至似乎都不知道它存在!”前两个类别并不重要。

7 个答案:

答案 0 :(得分:35)

您可能想要试用vulture。由于Python的动态特性,它无法捕捉到所有内容,但它需要相当多,而不需要像coverage.py这样的完整测试套件,而其他人需要工作。

答案 1 :(得分:15)

尝试运行Ned Batcheldercoverage.py

  

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

答案 2 :(得分:7)

在不执行代码的情况下很难确定调用哪些函数和方法,即使代码没有做任何花哨的东西。普通函数调用很容易检测,但方法调用真的很难。只是一个简单的例子:

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项目都使用类和方法,所以它几乎没有帮助。

答案 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 say)

它是丑陋的,它只给出了大致的答案,但我认为这对于一个开始是好的。你们有什么想法?

答案 4 :(得分:4)

使用以下行,您可以列出显然不用作属性,函数调用,装饰器或返回值的所有函数定义。所以它几乎就是你要找的东西。这不是完美的,它很慢,但我从来没有得到任何误报。 (使用linux,您必须将ack替换为ack-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

答案 5 :(得分:1)

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

答案 6 :(得分:1)

使用简单的pylint插件可以很快实现IMO:

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

然后你必须在所有代码库上调用pylint来获得有意义的东西。 当然,如上所述,这需要检查,因为可能存在推理失败或 这会引入假阳性。无论如何,这可能会大大减少要完成的grep数量。

我自己没有太多时间去做,但是有人会在python-projects@logilab.org邮件列表上找到帮助。