Python 中的静态分析器

静态分析器是帮助您检查代码而无需实际运行代码的工具。最基本的静态分析器形式是您常用编辑器中的语法高亮显示器。如果您需要编译代码(例如,在 C++ 中),您的编译器(例如 LLVM)也可能提供一些静态分析器功能,以警告您潜在的问题(例如,在 C++ 中将赋值符“=”误用为相等符“==”)。在 Python 中,我们有一些工具可以识别潜在的错误或指出违反编码标准的地方。

完成本教程后,您将了解其中一些工具。具体来说,

  • Pylint、Flake8 和 mypy 工具能做什么?
  • 什么是编码风格违规?
  • 我们如何使用类型提示来帮助分析器识别潜在错误?

使用我的新书《Python for Machine Learning启动您的项目,其中包括逐步教程和所有示例的Python 源代码文件。

让我们开始吧。

Python 中的静态分析器
图片来源:Skylar Kang。保留部分权利

概述

本教程分为三个部分;它们是

  • Pylint 简介
  • Flake8 简介
  • mypy 简介

Pylint

Lint 是很久以前为 C 创建的静态分析器的名称。Pylint 借鉴了它的名字,是使用最广泛的静态分析器之一。它作为 Python 包提供,我们可以使用 pip 安装它

然后我们的系统中就可以使用 pylint 命令了。

Pylint 可以检查一个脚本或整个目录。例如,如果我们将以下脚本保存为 lenet5-notworking.py

我们可以要求 Pylint 在运行代码之前告诉我们代码的质量如何

输出如下

如果您向 Pylint 提供模块的根目录,模块的所有组件都将由 Pylint 检查。在这种情况下,您将在每一行的开头看到不同文件的路径。

这里有几点需要注意。首先,Pylint 的抱怨分为不同的类别。最常见的是关于约定(即风格问题)、警告(即代码可能以与您预期不一致的方式运行)和错误(即代码可能无法运行并抛出异常)。它们由 E0601 等代码标识,其中第一个字母是类别。

Pylint 可能会给出误报。在上面的示例中,我们看到 Pylint 将从 tensorflow.keras.datasets 导入标记为错误。这是由 Tensorflow 包中的优化引起的,当我们导入 Tensorflow 时,Python 不会扫描和加载所有内容,而是创建一个 LazyLoader 来帮助只加载大型包的必要部分。这大大节省了程序启动时间,但也使 Pylint 感到困惑,因为我们似乎导入了不存在的东西。

此外,Pylint 的主要功能之一是帮助我们将代码与 PEP8 编码风格对齐。例如,当我们定义一个没有文档字符串的函数时,即使代码没有做错任何事情,Pylint 也会抱怨我们没有遵循编码约定。

但 Pylint 最重要的用途是帮助我们识别潜在问题。例如,我们将 y_train 拼写错误为大写 YY_train。Pylint 会告诉我们正在使用一个未赋值的变量。它没有直接告诉我们哪里出了问题,但它确实指出了正确的位置来校对我们的代码。同样,当我们在第 23 行定义变量 model 时,Pylint 告诉我们外部作用域中存在一个同名变量。因此,稍后对 model 的引用可能与我们所想的不同。同样,未使用的导入可能仅仅是因为我们拼错了模块的名称。

所有这些都是 Pylint 提供的提示。我们仍然需要自行判断来更正代码(或忽略 Pylint 的抱怨)。

但是,如果您知道 Pylint 应该停止抱怨什么,您可以请求忽略这些。例如,我们知道 import 语句是正确的,所以我们可以这样调用 Pylint

现在,Pylint 将忽略所有代码为 E0611 的错误。您可以通过逗号分隔列表禁用多个代码,例如,

如果您只想在代码的特定行或特定部分禁用某些问题,您可以在代码中添加特殊注释,如下所示

魔法关键字 pylint: 将引入 Pylint 特定的指令。代码 E0611 和名称 no-name-in-module 是相同的。在上面的示例中,Pylint 将抱怨最后两个导入语句,而不是前两个,因为有这些特殊注释。

Flake8

Flake8 工具实际上是 PyFlakes、McCabe 和 pycodestyle 的包装器。当您使用以下命令安装 flake8 时

您将安装所有这些依赖项。

与 Pylint 类似,安装此软件包后,我们就可以使用 flake8 命令,并且可以传入脚本或目录进行分析。但 Flake8 的重点倾向于编码风格。因此,对于上面相同的代码,我们将看到以下输出

以字母 E 开头的错误代码来自 pycodestyle,以字母 F 开头的错误代码来自 PyFlakes。我们可以看到它抱怨编码风格问题,例如使用 (5,5) 时逗号后没有空格。我们还可以看到它能够识别在赋值前使用变量的情况。但它没有捕获一些代码异味,例如函数 createmodel() 重复使用了在外部作用域中已经定义的变量 model

想开始学习机器学习 Python 吗?

立即参加我为期7天的免费电子邮件速成课程(附示例代码)。

点击注册,同时获得该课程的免费PDF电子书版本。

与 Pylint 类似,我们也可以要求 Flake8 忽略一些抱怨。例如,

这些行将不会在输出中打印

我们也可以使用魔法注释来禁用一些抱怨,例如,

Flake8 将查找注释 # noqa: 以跳过这些特定行上的某些抱怨。

Mypy

Python 不是一种类型语言,因此与 C 或 Java 不同,您不需要在使用前声明某些函数或变量的类型。但最近,Python 引入了类型提示符号,因此我们可以指定函数或变量预期的类型,而无需像类型语言那样强制其符合性。

想开始学习机器学习 Python 吗?

立即参加我为期7天的免费电子邮件速成课程(附示例代码)。

点击注册,同时获得该课程的免费PDF电子书版本。

在 Python 中使用类型提示的最大好处之一是为静态分析器提供额外的检查信息。Mypy 是能够理解类型提示的工具。即使没有类型提示,Mypy 仍然可以提供类似于 Pylint 和 Flake8 的抱怨。

我们可以从 PyPI 安装 Mypy

然后可以将上面的示例提供给 mypy 命令

我们看到了与上面的 Pylint 类似的错误,尽管有时不如 Pylint 精确(例如,变量y_train的问题)。然而,我们在上面看到 mypy 的一个特点:它期望我们使用的所有库都带有一个存根,以便可以进行类型检查。这是因为类型提示是可选的。如果库中的代码没有提供类型提示,代码仍然可以工作,但 mypy 无法验证。一些库有可用的类型存根,使 mypy 可以更好地检查它们。

我们再考虑一个例子

这个程序应该加载一个 HDF5 文件(例如 Keras 模型),并打印其中存储的所有属性和数据。我们使用了h5py模块(它没有类型存根,因此 mypy 无法识别它使用的类型),但我们为我们定义的函数dumphdf5()添加了类型提示。这个函数期望一个 HDF5 文件的文件名,并打印其中存储的所有内容。最后,将返回存储的数据集数量。

当我们将此脚本保存为dumphdf5.py并将其传递给 mypy 时,我们将看到以下内容

我们误用了我们的函数,将一个已打开的文件对象传递给了dumphdf5(),而不是仅仅传递文件名(作为字符串)。Mypy 可以识别这个错误。我们还声明了函数应该返回一个整数,但我们没有在函数中添加返回语句。

然而,这段代码中还有一个 mypy 没有识别出的错误。即,在内部函数recur_dump()中使用变量count应该声明为nonlocal,因为它是在作用域外定义的。这个错误可以被 Pylint 和 Flake8 捕捉到,但 mypy 却漏掉了。

以下是没有更多错误的完整、已更正的代码。请注意,我们在第一行添加了神奇注释“# type: ignore”,以消除 mypy 的类型存根警告

总之,我们上面介绍的这三种工具可以相互补充。您可以考虑运行所有这些工具来查找代码中可能存在的任何错误或改进编码风格。每种工具都允许通过命令行或配置文件进行一些配置,以根据您的需求进行自定义(例如,一行代码多长才算太长而值得发出警告?)。使用静态分析器也是帮助您提高编程技能的一种方式。

延伸阅读

如果您想深入了解,本节提供了更多关于该主题的资源。

文章

软件包

总结

在本教程中,您已经了解了一些常见的静态分析器如何帮助您编写更好的 Python 代码。具体来说,您学习了

  • 三种工具的优缺点:Pylint、Flake8 和 mypy
  • 如何自定义这些工具的行为
  • 如何理解这些分析器提出的问题

掌握机器学习 Python!

Python For Machine Learning

更自信地用 Python 编写代码

...从学习实用的 Python 技巧开始

在我的新电子书中探索如何实现
用于机器学习的 Python

它提供自学教程数百个可运行的代码,为您提供包括以下技能:
调试性能分析鸭子类型装饰器部署等等...

向您展示高级 Python 工具箱,用于
您的项目


查看内容

Python 中的静态分析器的一条回复

  1. Blaine Bateman 2022 年 5 月 13 日上午 7:04 #

    不错

发表评论

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。