77

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

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

我知道找到所有死代码是无法确定的。我所需要的只是一个工具,它可以找到所有未在任何地方调用的函数。我们并没有对基于函数名称字符串的调用函数做任何花哨的事情,所以我不担心任何病态......

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

显然,我可以在所有文件中 grep for def,从中获取所有函数名,然后对每个函数名执行 grep。我只是希望已经有比这更聪明的东西了。

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

4

7 回答 7

44

你可能想试试vulture。由于 Python 的动态特性,它无法捕获所有内容,但它可以捕获很多内容,而无需像 coverage.py 这样的完整测试套件和其他需要工作的套件。

于 2013-08-14T14:17:47.350 回答
16

尝试运行Ned Batcheldercoverage.py

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

于 2012-03-01T22:22:24.867 回答
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 项目都使用类和方法,所以它的帮助不大。

于 2012-03-02T16:58:17.273 回答
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 说)

它很丑陋,它只给了我大概的答案,但我认为这已经足够开始了。大家的想法是什么?

于 2012-03-02T16:13:43.660 回答
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
于 2013-08-06T13:57:17.643 回答
1

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

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

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

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

于 2012-03-02T08:14:39.333 回答
1

如果你的代码覆盖了很多测试(它非常有用),使用代码覆盖插件运行它们,然后你可以看到未使用的代码。)

于 2012-03-01T22:23:29.810 回答