百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

编写更多 pythonic 代码(十一)——Python 代码质量分析工具

suiw9 2025-01-27 00:16 21 浏览 0 评论


在本文中,我们将确定高质量的 Python 代码,并向您展示如何提高您自己的代码质量。

我们将分析和比较可用于将代码提升到新水平的工具。无论你是已经使用Python一段时间了,还是刚刚开始,你都可以从这里讨论的实践和工具中受益。

什么是代码质量?

当然,你想要高质量的代码,谁不会呢?但是为了提高代码质量,我们必须定义它是什么。

快速的谷歌搜索会产生许多定义代码质量的结果。事实证明,这个词对人们来说可能意味着许多不同的事情。

尝试定义代码质量的一种方法是就以下高质量的代码标识符达成一致:

  • 它做了它应该做的事情。
  • 它不包含缺陷或问题。
  • 它易于阅读、维护和扩展。

这三个标识符虽然简单化,但似乎已得到普遍同意。为了进一步扩展这些想法,让我们深入研究为什么每个想法在软件领域都很重要。

为什么代码质量很重要?

为了确定为什么高质量代码很重要,让我们重新审视这些标识符。我们将看到当代码不符合它们时会发生什么。

它没有做它应该做的事情

满足要求是任何产品、软件或其他方面的基础。我们制作软件来做某事。如果最后,它没有做到...好吧,这绝对不是高质量的。

它确实包含缺陷和问题

如果您正在使用的东西有问题或给您带来问题,您可能不会称之为高质量。事实上,如果它足够糟糕,你可能会完全停止使用它。

为了不使用软件作为示例,假设您的真空吸尘器在普通地毯上效果很好。它可以清除所有的灰尘和猫毛。一个决定命运的夜晚,猫撞倒了一株植物,到处都是泥土。当您尝试使用真空清洁污垢堆时,它会破裂,将污垢喷到各处。

虽然真空在某些情况下有效,但它不能有效地处理偶尔的额外负载。因此,您不会称其为高质量的真空吸尘器。

这是我们希望在代码中避免的问题。如果事情在边缘情况下出现问题并且缺陷导致不必要的行为,我们就没有高质量的产品。

难以阅读、维护或扩展

想象一下:客户请求一项新功能。编写原始代码的人已经走了。替换它们的人现在必须理解已经存在的代码。那个人就是你。

如果代码易于理解,您将能够分析问题并更快地提出解决方案。如果代码复杂且令人费解,则可能需要更长的时间,并可能做出一些错误的假设。

如果在不中断以前功能的情况下轻松添加新功能也很好。如果代码容易扩展,您的新功能可能会破坏其他内容。

没有人愿意处于必须阅读、维护或扩展低质量代码的位置。这意味着每个人的头痛和更多的工作。

你必须处理低质量的代码,但不要让其他人处于同样的情况,这已经够糟糕的了。您可以提高所编写代码的质量。

如果您与开发人员团队合作,则可以开始实施方法以确保更好的整体代码质量

如何提高 Python 代码质量

在我们的高质量代码之旅中,需要考虑一些事项。首先,这一旅程不是纯粹客观的旅程。对于高质量代码的外观,有一些强烈的感觉。

虽然每个人都希望就上述标识符达成一致,但他们实现的方式是一条主观的道路。当您谈论实现可读性、维护和可扩展性时,通常会出现最固执己见的主题。

所以请记住,虽然本文将始终保持客观,但在代码方面有一个非常固执己见的世界。

所以,让我们从最固执己见的话题开始:代码风格。

python风格指南

古老的问题开始:空格还是制表符?

无论您个人对如何表示空格的看法如何,都可以安全地假设您至少希望代码保持一致性。

风格指南用于定义编写代码的一致方式。通常,这都是表面的,这意味着它不会改变代码的逻辑结果。虽然,一些风格选择确实避免了常见的逻辑错误。

样式指南有助于实现使代码易于阅读、维护和扩展的目标

就Python而言,有一个被广泛接受的标准。它部分是由Python编程语言本身的作者编写的。

PEP 8 为 Python 代码提供了编码约定。Python 代码遵循此风格指南是相当常见的。这是一个很好的起点,因为它已经明确定义。

作为 Python 增强提案的姊妹篇,PEP 257 描述了 Python 文档字符串的约定,这些文档字符串是用于记录模块、类、函数和方法的字符串。作为额外的好处,如果文档字符串是一致的,有一些工具能够直接从代码生成文档。

所有这些指南所做的只是定义一种设置代码样式的方法。但是你如何执行它呢?那么代码中的缺陷和问题呢,你怎么能检测这些呢?这就是Linter的用武之地。

Linters

什么是Linter?

首先,让我们谈谈Linter。小错误、风格不一致和危险的逻辑不会让你的代码感觉很好。

但我们都会犯错。你不能指望自己总是及时抓住它们。键入错误的变量名称,忘记右括号,Python中的制表符不正确,使用错误的参数数调用函数,列表不胜枚举。Linter有助于识别这些问题区域。

此外,大多数编辑器和 IDE 能够在您键入时在后台运行 linter。这将生成一个能够在运行代码之前突出显示、下划线或以其他方式识别代码中的问题区域的环境。它就像对代码的高级拼写检查。它用波浪形的红线突出显示问题,就像您最喜欢的文字处理器一样。

Linter分析代码以检测各种类别的Linter。这些类别可以大致定义为以下内容:

  1. 逻辑Linter代码错误具有潜在意外结果的代码危险的代码模式
  2. 文体Linter代码不符合定义的约定

还有一些代码分析工具可提供对代码的其他见解。虽然根据定义可能不是linters,但这些工具通常与linters并排使用。他们也希望提高代码的质量。

最后,还有一些工具可以自动将代码格式化为某些规范。这些自动化工具确保我们劣等的人类思维不会搞砸惯例。

我的 Python 的 linter 选项是什么?

在深入研究您的选择之前,重要的是要认识到一些“linters”只是多个Linter很好地包装在一起。这些组合linters的一些流行示例如下:

Flake8:能够检测逻辑和风格上的Linter。它将 pycodestyle 的样式和复杂性检查添加到 PyFlakes 的逻辑 lint 检测中。它结合了以下Linter:

  • PyFlakes
  • pycodestyle(以前称为PEP8)
  • Mccabe

Pylama:由大量Linter和其他用于分析代码的工具组成的代码审计工具。它结合了以下内容:

  • pycodestyle(以前称为PEP8)
  • Pydocstyle(原PEP257)
  • PyFlakes
  • Mccabe
  • Pylint
  • Radon
  • gjslint

以下是一些独立的linters,并附有简要说明:

linter

类别

描述

Pylint

逻辑与风格

检查错误,尝试执行编码标准,查找代码异味

PyFlakes

逻辑

分析程序并检测各种错误

pycodestyle

文体

检查 PEP 8 中的一些样式约定

pydocstyle

文体

检查是否符合 Python 文档字符串约定

Bandit

逻辑

分析代码以查找常见的安全问题

MyPy

逻辑

检查可选强制的静态类型

以下是一些代码分析和格式化工具:

工具

类别

描述

Mccabe

分析

检查Mccabe的复杂性

Radon

分析

分析代码中的各种指标(代码行数、复杂性等)

Black

格式化程序

不折不扣地格式化 Python 代码

Isort

格式化程序

通过按字母顺序排序并分成几个部分来设置导入格式

对比 Python Linters

让我们更好地了解不同的linters能够捕捉到什么以及输出是什么样子的。为此,我使用默认设置在几个不同的 linters 中运行相同的代码。

我通过linters运行的代码如下。它包含各种逻辑和文体问题:

"""
code_with_lint.py
Example Code with lots of lint!
"""
import io
from math import *


from time import time
some_global_var = 'GLOBAL VAR NAMES SHOULD BE IN ALL_CAPS_WITH_UNDERSCOES'

def multiply(x, y):
    """
    This returns the result of a multiplation of the inputs
    """
    some_global_var = 'this is actually a local variable...'
    result = x* y
    return result
    if result == 777:
        print("jackpot!")

def is_sum_lucky(x, y):
    """This returns a string describing whether or not the sum of input is lucky
    This function first makes sure the inputs are valid and then calculates the
    sum. Then, it will determine a message to return based on whether or not
    that sum should be considered "lucky"
    """
    if x != None:
        if y is not None:
            result = x+y;
            if result == 7:
                return 'a lucky number!'
            else:
                return( 'an unlucky number!')

            return ('just a normal number')

class SomeClass:

    def __init__(self, some_arg,  some_other_arg, verbose = False):
        self.some_other_arg  =  some_other_arg
        self.some_arg        =  some_arg
        list_comprehension = [((100/value)*pi) for value in some_arg if value != 0]
        time = time()
        from datetime import datetime
        date_and_time = datetime.now()
        return


下面的比较显示了我使用的 linters 及其用于分析上述文件的运行时。我应该指出,这些并不完全可比,因为它们服务于不同的目的。例如,PyFlakes不会像Pylint那样识别风格错误。

linter

命令

时间

Pylint

Pylintcode_with_lint.py

1.16秒

PyFlakes

code_with_lint.py

0.15秒

pycodestyle

pycodestyle code_with_lint.py

0.14秒

pydocstyle

pydocstyle code_with_lint.py

0.21秒

有关每个输出,请参阅以下部分。

Pylint

Pylint是大约在2006年诞生,并且仍然维护良好。它已经存在了足够长的时间,贡献者已经修复了大多数主要的错误,而且核心功能也发展得很好。

对Pylint的普遍抱怨是,它很慢,默认情况下过于冗长,而且需要大量的配置才能让它按照你想要的方式工作。撇开速度不谈,其他的抱怨在某种程度上是一把双刃剑。啰嗦可能是因为彻底性。大量的配置可能意味着对你的喜好有很多适应性。

闲话少说,在上面的代码中运行Pylint后的输出:

No config file found, using default configuration
*** Module code_with_lint
W: 23, 0: Unnecessary semicolon (unnecessary-semicolon)
C: 27, 0: Unnecessary parens after 'return' keyword (superfluous-parens)
C: 27, 0: No space allowed after bracket
                return( 'an unlucky number!')
                      ^ (bad-whitespace)
C: 29, 0: Unnecessary parens after 'return' keyword (superfluous-parens)
C: 33, 0: Exactly one space required after comma
    def __init__(self, some_arg,  some_other_arg, verbose = False):
                               ^ (bad-whitespace)
C: 33, 0: No space allowed around keyword argument assignment
    def __init__(self, some_arg,  some_other_arg, verbose = False):
                                                          ^ (bad-whitespace)
C: 34, 0: Exactly one space required around assignment
        self.some_other_arg  =  some_other_arg
                             ^ (bad-whitespace)
C: 35, 0: Exactly one space required around assignment
        self.some_arg        =  some_arg
                             ^ (bad-whitespace)
C: 40, 0: Final newline missing (missing-final-newline)
W:  6, 0: Redefining built-in 'pow' (redefined-builtin)
W:  6, 0: Wildcard import math (wildcard-import)
C: 11, 0: Constant name "some_global_var" doesn't conform to UPPER_CASE naming style (invalid-name)
C: 13, 0: Argument name "x" doesn't conform to snake_case naming style (invalid-name)
C: 13, 0: Argument name "y" doesn't conform to snake_case naming style (invalid-name)
C: 13, 0: Missing function docstring (missing-docstring)
W: 14, 4: Redefining name 'some_global_var' from outer scope (line 11) (redefined-outer-name)
W: 17, 4: Unreachable code (unreachable)
W: 14, 4: Unused variable 'some_global_var' (unused-variable)
...
R: 24,12: Unnecessary "else" after "return" (no-else-return)
R: 20, 0: Either all return statements in a function should return an expression, or none of them should. (inconsistent-return-statements)
C: 31, 0: Missing class docstring (missing-docstring)
W: 37, 8: Redefining name 'time' from outer scope (line 9) (redefined-outer-name)
E: 37,15: Using variable 'time' before assignment (used-before-assignment)
W: 33,50: Unused argument 'verbose' (unused-argument)
W: 36, 8: Unused variable 'list_comprehension' (unused-variable)
W: 39, 8: Unused variable 'date_and_time' (unused-variable)
R: 31, 0: Too few public methods (0/2) (too-few-public-methods)
W:  5, 0: Unused import io (unused-import)
W:  6, 0: Unused import acos from wildcard import (unused-wildcard-import)
...
W:  9, 0: Unused time imported from time (unused-import)

请注意,Pylint在每个问题区域的前缀都加上了类别 :RCWEF

  • [R]违反“良好做法”指标的因素
  • [C]违反编码标准的行为
  • [W]寻找风格问题或小编程问题
  • [E]重要的编程问题(即很可能是错误)
  • [F]防止进一步处理的错误

上面的列表直接来自 Pylint 的用户指南。

PyFlakes

PPyflakes不会告诉你丢失的文档字符串或参数名称不符合命名风格。它专注于逻辑代码问题和潜在的错误。

这里的好处是速度。PyFlakes相比Pylint已经很快。

对上面的充满lint的代码运行后的输出:

code_with_lint.py:5: 'io' imported but unused
code_with_lint.py:6: 'from math import *' used; unable to detect undefined names
code_with_lint.py:14: local variable 'some_global_var' is assigned to but never used
code_with_lint.py:36: 'pi' may be undefined, or defined from star imports: math
code_with_lint.py:36: local variable 'list_comprehension' is assigned to but never used
code_with_lint.py:37: local variable 'time' (defined in enclosing scope on line 9) referenced before assignment
code_with_lint.py:37: local variable 'time' is assigned to but never used
code_with_lint.py:39: local variable 'date_and_time' is assigned to but never used

这里的缺点是解析各种问题和错误未按类型标记或组织。

pycodestyle(以前称为PEP8)

用于检查 PEP8 中的一些样式约定。不检查命名约定,也不检查文档字符串。它捕获的错误和警告在此表中分类。

针对上面填充的 lint 填充代码运行后的输出:

code_with_lint.py:13:1: E302 expected 2 blank lines, found 1
code_with_lint.py:15:15: E225 missing whitespace around operator
code_with_lint.py:20:1: E302 expected 2 blank lines, found 1
code_with_lint.py:21:10: E711 comparison to None should be 'if cond is not None:'
code_with_lint.py:23:25: E703 statement ends with a semicolon
code_with_lint.py:27:24: E201 whitespace after '('
code_with_lint.py:31:1: E302 expected 2 blank lines, found 1
code_with_lint.py:33:58: E251 unexpected spaces around keyword / parameter equals
code_with_lint.py:33:60: E251 unexpected spaces around keyword / parameter equals
code_with_lint.py:34:28: E221 multiple spaces before operator
code_with_lint.py:34:31: E222 multiple spaces after operator
code_with_lint.py:35:22: E221 multiple spaces before operator
code_with_lint.py:35:31: E222 multiple spaces after operator
code_with_lint.py:36:80: E501 line too long (83 > 79 characters)
code_with_lint.py:40:15: W292 no newline at end of file

这个输出的好处是 lint 是按类别标记的。如果您也不关心遵守特定约定,则可以选择忽略某些错误。

Pydocstyle(原PEP257)

与 pycodestyle 非常相似,只是它不是根据 PEP8 代码样式约定进行检查,而是根据 PEP257 的约定检查文档字符串。

针对上面填充的 lint 填充代码运行后的输出:

code_with_lint.py:1 at module level:
        D200: One-line docstring should fit on one line with quotes (found 3)
code_with_lint.py:1 at module level:
        D400: First line should end with a period (not '!')
code_with_lint.py:13 in public function `multiply`:
        D103: Missing docstring in public function
code_with_lint.py:20 in public function `is_sum_lucky`:
        D103: Missing docstring in public function
code_with_lint.py:31 in public class `SomeClass`:
        D101: Missing docstring in public class
code_with_lint.py:33 in public method `__init__`:
        D107: Missing docstring in __init__

同样,像 pycodestyle 一样,pydocstyle 对它发现的各种错误进行标签和分类。而且这个列表与 pycodestyle 的任何内容都不冲突,因为所有的错误都以 D 为前缀,代表 docstring。

何时可以检查我的代码质量?

您可以检查代码的质量:

  • 编写时自动检查
  • commit时进行检查
  • 运行测试时执行检查

让 linter 经常针对您的代码运行很有用。如果没有自动化和一致性,大型团队或项目很容易忽视目标并开始创建低质量的代码。当然,它发生得很慢。一些写得不好的逻辑,或者可能是一些格式与相邻代码不匹配的代码。随着时间的流逝,所有的问题都会堆积起来。最终,您可能会遇到一些有缺陷、难以阅读、难以修复且维护痛苦的东西。

为避免这种情况,请经常检查代码质量!

当你写代码时

您可以在编写代码时使用 linter,但配置环境以执行此操作可能需要一些额外的工作。这通常是为您的 IDE 或所选编辑器找到插件的问题。事实上,大多数 IDE 已经内置了 linter。

commit代码之前

如果您使用的是 Git,则可以将 Git 钩子设置为在提交之前运行您的 linter。其他版本控制系统具有类似的方法,可以在系统中的某些操作之前或之后运行脚本。可以使用这些方法来阻止任何不符合质量标准的新代码。

强制每一部分代码都经过Linter筛选是确保持续质量的重要一步。在代码的前门自动筛选可能是避免Linter填充代码的最佳方法。

运行测试时

您还可以将linters直接放置在可用于持续集成的任何系统中。如果代码不符合质量标准,可以将 linter 设置为使生成失败。

如果现有代码中已经存在大量 linter 错误那么CI可能失效,修复历史问题成本很高。为了解决这个问题,一些持续集成系统将允许您选择仅在新代码增加已存在的 linter 错误数量时才使构建失败。这样,您就可以开始提高质量,而无需对现有代码库进行完全重写。

结论

高质量的代码可以在不中断的情况下完成它应该做的事情。它易于阅读、维护和扩展。它的功能没有问题或缺陷,并且编写时便于下一个人使用。

有一些成熟的方法和工具可以帮助提高代码质量。

风格指南将为您的代码带来一致性。PEP8是Python的一个很好的起点。Linter将帮助您识别问题区域和不一致之处。您可以在整个开发过程中使用 linter,甚至可以自动执行它们以在 lint 填充代码走得太远之前对其进行标记。

让 linters 约束风格也避免了在代码审查期间进行风格讨论的需要。有些人可能会发现从这些工具而不是团队成员那里获得坦率的反馈更容易。此外,一些团队成员可能不想在代码审查期间“吹毛求疵”风格。Linter避免政治,节省时间,并抱怨任何不一致之处。

此外,本文中提到的所有 linter 都有各种命令行选项和配置,可让您根据自己的喜好定制工具。你可以随心所欲地严格或松散,这是一件需要意识到的重要事情。

提高代码质量是一个过程。您可以采取措施改进它,而无需完全禁止所有不符合标准的代码。

相关推荐

10款超实用JavaScript音频库(js播放音频代码)

HTML5提供了一种新的音频标签实现和规范用一个简单的HTML对象而无需音频插件来控制音频。这只是一个简单的整合这些新的HTML5音频特征及使用JavaScript来创建各种播放控制。下面将介绍10款...

Howler.js,一款神奇的 JavaScript 开源网络音频工具库

o...

PROFINET转Modbus网关——工业协议融合的智能枢纽

三格电子SG-PNh750-MOD-221,无缝连接Profinet与Modbus,赋能工业物联产品概述...

简单实用的Modbus类库,支持从站和DTU

一、简介...

[西门子PLC] S7-200 SMART PROFINET :通过GSD组态PLC设备

从S7-200SMARTV2.5版本开始,S7-200SMART开始支持做PROFINETIO通信的智能设备。从而,两个S7-200SMART之间可以进行PROFINETI...

Modbus(RTU / TCP)有什么异同(modbus tcp和tcp)

Modbus是一种广泛使用的工业自动化通信协议,它支持设备之间的数据交换。Modbus协议有两个主要的变体:ModbusRTU(二进制模式)和ModbusTCP(基于TCP/IP网络的模式)。尽管...

Modbus通信调试步骤详解(modbus调试工具怎么用)

Modbus通信调试步骤详解  Modbus通信分为串口和以太网,无论是串口还是以太网,只要是标准Modbus,就可以用Modbus模拟器进行调试。按以下几步进行调试。...

理解Intel手册汇编指令(intel 汇编指令手册)

指令格式...

「西门子PLC」S7-200 SMART的Modbus RTU通讯

S7-200SMART集成的RS485端口(端口0)以及SBCM01RS485/232信号板(端口1)两个通信端口可以同时做MODBUSRTU主站,或者一个做MODBUSRTU主站一个做MO...

InfiniBand网络运维全指南:从驱动安装到故障排查

一、InfiniBand网络概述InfiniBand(直译为“无限带宽”技术,缩写为IB)是一种用于高性能计算的计算机网络通信标准,具有极高的吞吐量和极低的延迟,用于计算机与计算机之间的数据互连。它...

一加回归 OPPO,背后的秘密不可告人

有这样一个手机品牌,它诞生于互联网品牌。在大众群体看来,它的身世似乎模糊不清,许多人以为它是国外品牌。它的产品定位是极客群体,深受国内发烧友,甚至国外极客玩家喜爱。...

[西门子PLC] S7-200SMART快速高效的完成Modbus通信程序的设计

一、导读Modbus通信是一种被广泛应用的通信协议,在变频器、智能仪表还有其他一些智能设备上都能见到它的身影。本文呢,就把S7-200SMART系列PLC当作Modbus主站,把...

狂肝10个月手搓GPU,他们在我的世界中玩起我的世界,梦想成真

梦晨衡宇萧箫发自凹非寺量子位|公众号QbitAI自从有人在《我的世界》里用红石电路造出CPU,就流传着一个梗:...

[西门子PLC] 博途TIA portal SCL编程基础入门:1-点动与自锁

一、S7-SCL编程语言简介...

工作原理系列之:Modbus(modbus工作过程)

MODBUS是一种在自动化工业中广泛应用的高速串行通信协议。该协议是由Modion公司(现在由施耐德电气公司获得)于1979年为自己的可编程逻辑控制器开发的。该协议充当了PLCS和智能自动化设备之间的...

取消回复欢迎 发表评论: