【问题标题】:Finding dead code in large python project [closed]在大型python项目中查找死代码[关闭]
【发布时间】:2012-03-20 11:01:00
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 已经有一个,已经开了 2.5 年了。我认为主要是因为他们知道在最困难的情况下,这在数学上是不可能的。
  • Grepping 听起来对你来说可能就足够了。或者也许使用parser 会给你更好的控制。

标签: python pylint code-cleanup


【解决方案1】:

您可能想试试vulture。由于 Python 的动态特性,它无法捕捉所有内容,但它不需要像 coverage.py 之类的完整测试套件和其他需要工作的完整测试套件就可以捕捉到很多东西。

【讨论】:

  • 这很完美,很高兴终于给出了真正的答案。 Vulture 执行原始提问者正在寻找的保守死代码分析
  • vulture 看起来很棒,但它并不能真正与 django 一起使用......不幸的是 django-coverage 插件太旧了,它需要很久以来已经失效的依赖项。 :(
【解决方案2】:

尝试运行Ned Batcheldercoverage.py

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

【讨论】:

  • 这将涉及在所有可能的配置中运行代码。我不想那样做。我不想要所有死代码的完整列表。我只想要一个快速而肮脏的近似值。留下一些死代码是好的。
  • "留下一些死代码很好" - 我真的不认为这很好。
  • 我认为留下死代码并不好,但几乎无法访问。
【解决方案3】:

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

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 项目都使用类和方法,所以它的帮助不大。

【讨论】:

  • 对类和异构列表的讨论是一个很好的讨论。我认为目前,我只想说“函数 f 被使用”,所以我将假设两者都是必要的。而且,如果我有 A 和 B,它们都具有函数 F,并且它们不在同一个异构列表中,或者可以互换使用,那么我的函数命名有问题......
  • 不确定我是否真的将此称为函数命名问题。举个标准库的例子:dict.get()queue.Queue.get()pickle.Pickler.get() 完全不相关。不知何故,命名空间的全部意义在于允许对不同的事物使用相同的名称。
  • 好的,很公平。我想我假设具有标准名称(如 get、set、equals、init 等)的东西都会在某个地方使用,所以我并不担心这些。但是,是的,你是对的。
【解决方案4】:

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

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

然后我看一下引用很少的单个函数(

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

【讨论】:

  • 我应该提到 for 循环是 bash 语法。
  • 注意:由于很多原因,这段代码完全不起作用..其中最重要的是它有语法错误并且正在寻找错误的东西(应该只是函数名称等)...不要运行它。但这仍然是一个不错的主意...简单的字符串计数等。
  • 我说这只是大概的......
  • 脚本的修改版本。没有手册 - gist.github.com/peeyushsrj/87f90919e489994572eafaf75c10c9d5
【解决方案5】:

通过以下行,您可以列出所有显然不用作属性、函数调用、装饰器或返回值的函数定义。所以它大约是你正在寻找的东西。它并不完美,它很慢,但我从来没有得到任何误报。 (使用 linux 你必须用ack-grep 替换ack

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

【讨论】:

  • 你可以通过用grep --include "*.py"替换ack来提高速度
【解决方案6】:

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

【讨论】:

  • 除非您可能对死代码进行测试。即它不在系统其余部分的任何地方使用
  • 我在代码中的测试很少。添加单元测试是我在弄清楚实际需要测试什么之后应该做的事情......如果我测试所有死代码,我将有效地将它恢复生机,这不是我要做的想要。
【解决方案7】:

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

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

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

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

【讨论】:

  • FWIW 我过去写过 pylint 插件(不是为了这个,而是为了做其他事情),虽然不是微不足道的(你在检查代码的 AST 上写断言),但它们更容易获得工作超出我的预期。
猜你喜欢
  • 2011-01-23
  • 2010-09-14
  • 1970-01-01
  • 1970-01-01
  • 2010-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-29
相关资源
最近更新 更多