python重构

Do you want simpler Python code? You always start a project with the best intentions, a clean codebase, and a nice structure. But over time, there are changes to your apps, and things can get a little messy.

您是否需要更简单的Python代码? 您始终以最好的意图,干净的代码库和良好的结构启动项目。 但是随着时间的流逝,您的应用程序会发生变化,事情可能会变得有些混乱。

If you can write and maintain clean, simple Python code, then it’ll save you lots of time in the long term. You can spend less time testing, finding bugs, and making changes when your code is well laid out and simple to follow.

如果您可以编写和维护简洁的Python代码,那么从长远来看,它将节省大量时间。 当您的代码布局合理且易于遵循时,您可以花费更少的时间进行测试,查找错误以及进行更改。

In this tutorial you’ll learn:

在本教程中,您将学习:

  • How to measure the complexity of Python code and your applications
  • How to change your code without breaking it
  • What the common issues in Python code that cause extra complexity are and how you can fix them
  • 如何衡量Python代码和应用程​​序的复杂性
  • 如何在不破坏代码的情况下更改代码
  • Python代码中导致额外复杂性的常见问题是什么以及如何解决它们

Throughout this tutorial, I’m going to use the theme of subterranean railway networks to explain complexity because navigating a subway system in a large city can be complicated! Some are well designed, and others seem overly complex.

在整个教程中,我将使用地下铁路网络的主题来解释复杂性,因为在大城市中导航地铁系统可能会很复杂! 有些设计合理,而另一些似乎过于复杂。

Free Bonus: 5 Thoughts On Python Mastery, a free course for Python developers that shows you the roadmap and the mindset you’ll need to take your Python skills to the next level.

免费奖金: 关于Python精通的5个想法 ,这是针对Python开发人员的免费课程,向您展示了将Python技能提升到新水平所需的路线图和心态。

Python中的代码复杂度 (Code Complexity in Python)

The complexity of an application and its codebase is relative to the task it’s performing. If you’re writing code for NASA’s jet propulsion laboratory (literally rocket science), then it’s going to be complicated.

应用程序及其代码库的复杂性与它正在执行的任务有关。 如果您正在为NASA的喷气推进实验室(实际上是火箭科学 )编写代码,那么它将变得很复杂。

The question isn’t so much, “Is my code complicated?” as, “Is my code more complicated than it needs to be?”

问题不是很多,“我的代码复杂吗?” 如:“我的代码是否比需要的复杂?”

The Tokyo railway network is one of the most extensive and complicated in the world. This is partly because Tokyo is a metropolis of over 30 million people, but it’s also because there are 3 networks overlapping each other.

东京铁路网是世界上最广泛和最复杂的铁路网之一。 一方面是因为东京是一个人口超过3000万的大都市,另一方面是因为三个网络相互重叠。

The author of this article getting lost on the Tokyo Metro
本文的作者在东京地铁上迷路了

There are the Toei and Tokyo Metro rapid-transport networks as well as the Japan Rail East trains going through Central Tokyo. To even the most experienced traveler, navigating central Tokyo can be mind-bogglingly complicated.

有都营和东京地铁的快速交通网络,以及穿越东京市中心的日本东部铁路。 对于即使是最有经验的旅行者来说,前往东京市中心也可能非常复杂。

Here is a map of the Tokyo railway network to give you some perspective:

这是东京铁路网的地图,为您提供一些角度:

Tokyo Metro Co.东京地铁公司

If your code is starting to look a bit like this map, then this is the tutorial for you.

如果您的代码开始看起来有点像这张地图,那么这就是您的指南。

First, we’ll go through 4 metrics of complexity that can give you a scale to measure your relative progress in the mission to make your code simpler:

首先,我们将介绍4种复杂性指标,这些指标可以给您一个规模,以衡量您在任务中的相对进度,从而使您的代码更简单:

After you’ve explored the metrics, you’ll learn about a tool called wily to automate calculating those metrics.

探索指标之后,您将了解一个名为wily的工具,该工具可以自动计算这些指标。

衡量复杂性的指标 (Metrics for Measuring Complexity)

Much time and research have been put into analyzing the complexity of computer software. Overly complex and unmaintainable applications can have a very real cost.

分析计算机软件的复杂性已经花费了很多时间和研究。 过于复杂且无法维护的应用程序可能会带来很高的实际成本。

The complexity of software correlates to the quality. Code that is easy to read and understand is more likely to be updated by developers in the future.

软件的复杂性与质量相关。 易于阅读和理解的代码将来很可能由开发人员更新。

Here are some metrics for programming languages. They apply to many languages, not just Python.

以下是一些编程语言的指标。 它们适用于多种语言,而不仅仅是Python。

代码行 (Lines of Code)

LOC, or Lines of Code, is the crudest measure of complexity. It is debatable whether there is any direct correlation between the lines of code and the complexity of an application, but the indirect correlation is clear. After all, a program with 5 lines is likely simpler than one with 5 million.

LOC或代码行是最复杂的度量。 在代码行和应用程序的复杂性之间是否存在任何直接关联,这是有争议的,但是间接关联是显而易见的。 毕竟,一个有5行的程序可能要比有500万行的程序简单。

When looking at Python metrics, we try to ignore blank lines and lines containing comments.

在查看Python指标时,我们尝试忽略空白行和包含注释的行。

Lines of code can be calculated using the wc command on Linux and Mac OS, where file.py is the name of the file you want to measure:

可以在Linux和Mac OS上使用wc命令来计算代码行,其中file.py是要测量的文件的名称:

 $ wc -l file.py
$ wc -l file.py

If you want to add the combined lines in a folder by recursively searching for all .py files, you can combine wc with the find command:

如果要通过递归搜索所有.py文件来将组合行添加到文件夹中,可以将wcfind命令结合使用:

For Windows, PowerShell offers a word count command in Measure-Object and a recursive file search in Get-ChildItem:

对于Windows,PowerShell在Measure-Object提供了单词计数命令,并在Get-ChildItem了递归文件搜索:

 $ $ Get-ChildItem Get-ChildItem -Path -Path *.*. py py -Recurse -Recurse | | Measure-Object Measure-Object –– Line
Line

In the response, you will see the total number of lines.

在响应中,您将看到总行数。

Why are lines of code used to quantify the amount of code in your application? The assumption is that a line of code roughly equates to a statement. Lines is a better measure than characters, which would include whitespace.

为什么使用代码行来量化应用程序中的代码量? 假设代码行大致等于一条语句。 行号比字符(包括空格)更好。

In Python, we are encouraged to put a single statement on each line. This example is 9 lines of code:

在Python中,我们鼓励在每行上放一个语句。 此示例是9行代码:

If you used only lines of code as your measure of complexity, it could encourage the wrong behaviors.

如果仅使用代码行来衡量复杂性,则可能会导致错误行为。

Python code should be easy to read and understand. Taking that last example, you could reduce the number of lines of code to 3:

Python代码应该易于阅读和理解。 以最后一个示例为例,您可以将代码行数减少为3:

 x x = = 55 ; ; y y = = intint (( inputinput (( "Enter a number:""Enter a number:" ))
))
equality equality = = "is equal to" "is equal to" if if x x == == y y else else "is less than" "is less than" if if x x < < y y else else "is more than"
"is more than"
printprint (( ff "" {x}{x}    {equality}{equality}    {y}{y} "" )
)

But the result is hard to read, and PEP 8 has guidelines around maximum line length and line breaking. You can check out How to Write Beautiful Python Code With PEP 8 for more on PEP 8.

但是结果很难读懂,PEP 8有关于最大行长和断行的准则。 您可以查看如何使用PEP 8编写漂亮的Python代码,以获取有关PEP 8的更多信息。

This code block uses 2 Python language features to make the code shorter:

此代码块使用2种Python语言功能来简化代码:

  • Compound statements: using ;
  • Chained conditional or ternary statements: name = value if condition else value if condition2 else value2
  • 复合语句:使用;
  • 链接的条件或三元语句: name = value if condition else value if condition2 else value2

We have reduced the number of lines of code but violated one of the fundamental laws of Python:

我们减少了代码行的数量,但违反了Python的基本法则之一:

“Readability counts”

“可读性很重要”

— Tim Peters, Zen of Python

— Tim Zens,Python的Zen

This shortened code is potentially harder to maintain because code maintainers are humans, and this short code is harder to read. We will explore some more advanced and useful metrics for complexity.

因为代码维护者是人类,所以缩短的代码可能更难以维护,而这种短代码则更难阅读。 我们将探讨一些更高级,更有用的度量指标。

圈复杂度 (Cyclomatic Complexity)

Cyclomatic complexity is the measure of how many independent code paths there are through your application. A path is a sequence of statements that the interpreter can follow to get to the end of the application.

循环复杂度是衡量应用程序中有多少个独立代码路径的度量。 路径是解释器可以遵循的语句序列,以到达应用程序的结尾。

One way to think of cyclomatic complexity and code paths is imagine your code is like a railway network.

考虑圈复杂度和代码路径的一种方法是,假设您的代码就像一个铁路网络。

For a journey, you may need to change trains to reach your destination. The Lisbon Metropolitan railway system in Portugal is simple and easy to navigate. The cyclomatic complexity for any trip is equal to the number of lines you need to travel on:

对于旅途,您可能需要更改火车才能到达目的地。 葡萄牙的里斯本大都会铁路系统简单易行。 任何行程的圈复杂度等于您需要行进的线路数:

Metro Lisboa葡京

If you needed to get from Alvalade to Anjos, then you would travel 5 stops on the linha verde (green line):

如果您需要从Alvalade到Anjos,那么您将在linha verde(绿线)上行驶5个站:

Metro Lisboa葡京

This trip has a cyclomatic complexity of 1 because you only take 1 train. It’s an easy trip. That train is equivalent in this analogy to a code branch.

此行程的圈复杂度为1,因为您只乘1列火车。 这是一个轻松的旅程。 在这种类比中,该列车等效于代码分支。

If you needed to travel from the Aeroporto (airport) to sample the food in the district of Belém, then it’s a more complicated journey. You would have to change trains at Alameda and Cais do Sodré:

如果您需要从机场(机场)出发在贝伦区(Belém )品尝食物 ,那将是一个更加复杂的旅程。 您将不得不在Alameda和Cais doSodré换乘火车:

Metro Lisboa葡京

This trip has a cyclomatic complexity of 3, because you take 3 trains. You might be better off taking a taxi!

此行程的圈内复杂度为3,因为您需要乘坐3列火车。 您最好乘出租车去!

Seeing as how you’re not navigating Lisbon, but rather writing code, the changes of train line become a branch in execution, like an if statement. Let’s explore this example:

就像您不是在浏览里斯本,而是在编写代码一样,更改火车线路成为执行中的一个分支,就像if语句一样。 让我们研究这个例子:

There is only 1 way this code can be executed, so it has a cyclomatic complexity of 1.

该代码只有一种执行方式,因此圈复杂度为1。

If we add a decision, or branch to the code as an if statement, it increases the complexity:

如果我们添加一个决定,或者以if语句的形式分支到代码,则将增加复杂性:

 x x = = 1
1
if if x x < < 22 ::x x += += 1
1

Even though there is only 1 way this code can be executed, as x is a constant, this has a cyclomatic complexity of 2. All of the cyclomatic complexity analyzers will treat an if statement as a branch.

即使只有一种方式可以执行此代码,因为x是一个常数,所以它的循环复杂度为2。所有循环复杂度分析器都将if语句视为分支。

This is also an example of overly complex code. The if statement is useless as x has a fixed value. You could simply refactor this example to the following:

这也是过于复杂的代码的示例。 if语句无用,因为x具有固定值。 您可以简单地将此示例重构为以下内容:

That was a toy example, so let’s explore something a little more real.

那是一个玩具的例子,所以让我们探索一些更真实的东西。

main() has a cyclomatic complexity of 5. I’ll comment each branch in the code so you can see where they are:

main()的循环复杂度为5。我将注释代码中的每个分支,以便您可以看到它们的位置:

 # cyclomatic_example.py
# cyclomatic_example.py
import import syssysdef def mainmain ():
():
if if lenlen (( syssys .. argvargv ) ) > > 11 :  :  # 1
# 1
filepath filepath = = syssys .. argvargv [[ 11 ]]elseelse ::printprint (( "Provide a file path""Provide a file path" ))exitexit (( 11 )
)
if if filepathfilepath :  :  # 2
# 2
with with openopen (( filepathfilepath ) ) as as fpfp :  :  # 3
# 3
for for line line in in fpfp .. readlinesreadlines ():  ():  # 4
# 4
if if line line != != "" nn "" :  :  # 5
# 5
printprint (( lineline , , endend == """" ))if if __name__ __name__ == == "__main__""__main__" :  :  # Ignored.# Ignored.mainmain ()
()

There are certainly ways that code can be refactored into a far simpler alternative. We’ll get to that later.

当然,可以通过多种方式将代码重构为更简单的替代方法。 我们稍后再讨论。

Note: The Cyclomatic Complexity measure was developed by Thomas J. McCabe, Sr in 1976. You may see it referred to as the McCabe metric or McCabe number.

注意: Cyclomatic Complexity度量由Thomas J. McCabe,Sr在1976年开发 。您可能会看到它被称为McCabe度量McCabe数

In the following examples, we will use the radon library from PyPi to calculate metrics. You can install it now:

在以下示例中,我们将使用PyPi中的radon库计算指标。 您可以立即安装:

To calculate cyclomatic complexity using radon, you can save the example into a file called cyclomatic_example.py and use radon from the command line.

要使用radon计算圈复杂度,您可以将示例保存到名为cyclomatic_example.py的文件中,并从命令行使用radon

The radon command takes 2 main arguments:

radon命令采用两个主要参数:

  1. The type of analysis (cc for cyclomatic complexity)
  2. A path to the file or folder to analyze
  1. 分析类型( cc代表复杂的圈数)
  2. 要分析的文件或文件夹的路径

Execute the radon command with the cc analysis against the cyclomatic_example.py file. Adding -s will give the cyclomatic complexity in the output:

cyclomatic_example.py文件执行带有cc分析的radon命令。 添加-s将在输出中给出圈复杂度:

 $ radon cc cyclomatic_example.py -s
$ radon cc cyclomatic_example.py -s
cyclomatic_example.py
cyclomatic_example.py
    F 4:0 main - B (6)
    F 4:0 main - B (6)

The output is a little cryptic. Here is what each part means:

输出有点神秘。 这是每个部分的含义:

  • F means function, M means method, and C means class.
  • main is the name of the function.
  • 4 is the line the function starts on.
  • B is the rating from A to F. A is the best grade, meaning the least complexity.
  • The number in parentheses, 6, is the cyclomatic complexity of the code.
  • F表示函数, M表示方法, C表示类。
  • main是函数的名称。
  • 4是功能开始的行。
  • B是从A到F的等级。A是最好的等级,意味着最小的复杂性。
  • 括号中的数字6是代码的圈复杂度。

Halstead指标 (Halstead Metrics)

The Halstead complexity metrics relate to the size of a program’s codebase. They were developed by Maurice H. Halstead in 1977. There are 4 measures in the Halstead equations:

Halstead复杂性指标与程序代码库的大小有关。 它们是由Maurice H. Halstead在1977年开发的。Halstead方程中有4个测度:

  • Operands are values and names of variables.
  • Operators are all of the built-in keywords, like if, else, for or while.
  • Length (N) is the number of operators plus the number of operands in your program.
  • Vocabulary (h) is the number of unique operators plus the number of unique operands in your a program.
  • 操作数是变量的值和名称。
  • 运算符是所有内置关键字,例如ifelseforwhile
  • 长度(N)是运算符的数量加上程序中操作数的数量。
  • 词汇量(h)是程序中唯一运算符的数量加上唯一运算数的数量。

There are then 3 additional metrics with those measures:

这些指标还有3个其他指标:

  • Volume (V) represents a product of the length and the vocabulary.
  • Difficulty (D) represents a product of half the unique operands and the reuse of operands.
  • Effort (E) is the overall metric that is a product of volume and difficulty.
  • 体积(V)表示长度词汇量的乘积。
  • 难度(D)表示唯一操作数和操作数重用的一半的乘积。
  • 努力(E)数量难度乘积的总体指标。

All of this is very abstract, so let’s put it in relative terms:

所有这些都是非常抽象的,因此让我们用相对的术语来表示:

  • The effort of your application is highest if you use a lot of operators and unique operands.
  • The effort of your application is lower if you use a few operators and fewer variables.
  • 如果使用大量运算符和唯一操作数,则应用程序的工作量最大。
  • 如果使用较少的运算符和较少的变量,则应用程序的工作量会减少。

For the cyclomatic_complexity.py example, operators and operands both occur on the first line:

对于cyclomatic_complexity.py示例,运算符和操作数都出现在第一行:

import is an operator, and sys is the name of the module, so it’s an operand.

import是一个运算符, sys是模块的名称,因此它是一个操作数。

In a slightly more complex example, there are a number of operators and operands:

在稍微复杂一点的示例中,有许多运算符和操作数:

 if if lenlen (( syssys .. argvargv ) ) > > 11 ::...
...

There are 5 operators in this example:

此示例中有5个运算符:

  1. if
  2. (
  3. )
  4. >
  5. :
  1. if
  2. (
  3. )
  4. >
  5. :

Furthermore, there are 2 operands:

此外,还有2个操作数:

  1. sys.argv
  2. 0
  1. sys.argv
  2. 0

Be aware that radon only counts a subset of operators. For example, parentheses are excluded in any calculations.

请注意, radon只计算运算符的一个子集。 例如,括号不包括在任何计算中。

To calculate the Halstead measures in radon, you can run the following command:

要计算radon的Halstead度量,可以运行以下命令:

Why does radon give a metric for time and bugs?

为什么radon给出的时间和错误的指标?

Halstead theorized that you could estimate the time taken in seconds to code by dividing the effort (E) by 18.

霍尔斯特德(Halstead)理论认为,通过将工作量( E )除以18,可以估算出以秒为单位的编码时间。

Halstead also stated that the expected number of bugs could be estimated dividing the volume (V) by 3000. Keep in mind this was written in 1977, before Python was even invented! So don’t panic and start looking for bugs just yet.

Halstead还表示,可以估计预期的bug数量除以体积( V )除以3000。请记住,这是在1977年Python发明之前编写的! 因此,不要惊慌,现在就开始寻找错误。

维修性指数 (Maintainability Index)

The maintainability index brings the McCabe Cyclomatic Complexity and the Halstead Volume measures in a scale roughly between zero and one-hundred.

可维护性指数使McCabe圈复杂度和Halstead体积度量大致在零到一百之间。

If you’re interested, the original equation is as follows:

如果您有兴趣,原始方程式如下:

In the equation, V is the Halstead volume metric, C is the cyclomatic complexity, and L is the number of lines of code.

在等式中, V是哈尔斯特德体积度量, C是圈复杂度, L是代码行数。

If you’re as baffled as I was when I first saw this equation, here’s it means: it calculates a scale that includes the number of variables, operations, decision paths, and lines of code.

如果您像我第一次看到这个方程式时一样困惑,那就意味着:它计算出一个包含变量,操作,决策路径和代码行数的比例。

It is used across many tools and languages, so it’s one of the more standard metrics. However, there are numerous revisions of the equation, so the exact number shouldn’t be taken as fact. radon, wily, and Visual Studio cap the number between 0 and 100.

它被许多工具和语言所使用,因此它是更标准的指标之一。 但是,该方程式有许多修改,因此不应将确切数字视为事实。 radonwily和Visual Studio将数字限制在0到100之间。

On the maintainability index scale, all you need to be paying attention to is when your code is getting significantly lower (toward 0). The scale considers anything lower than 25 as hard to maintain, and anything over 75 as easy to maintain. The Maintainability Index is also referred to as MI.

在可维护性指标规模上,您需要注意的只是代码变得越来越低(接近0)时。 该秤认为低于25的东西难以维护 ,超过75的东西易于维护 。 可维护性指数也称为MI

The maintainability index can be used as a measure to get the current maintainability of your application and see if you’re making progress as you refactor it.

可维护性指数可以用作衡量应用程序当前可维护性的指标,并在重构时查看您是否正在取得进展。

To calculate the maintainability index from radon, run the following command:

要从radon计算可维护性指数,请运行以下命令:

 $ radon mi cyclomatic_example.py -s
$ radon mi cyclomatic_example.py -s
cyclomatic_example.py - A (87.42)
cyclomatic_example.py - A (87.42)

In this result, A is the grade that radon has applied to the number 87.42 on a scale. On this scale, A is most maintainable and F the least.

在该结果, A是等级即radon已施加到数87.42上的刻度。 在此规模上, A最易于维护, F最少。

wily地捕获和跟踪项目的复杂性 (Using wily to Capture and Track Your Projects’ Complexity)

wily is an open-source software project for collecting code-complexity metrics, including the ones we’ve covered so far like Halstead, Cyclomatic, and LOC. wily integrates with Git and can automate the collection of metrics across Git branches and revisions.

wily是一个开放源代码的软件项目,用于收集代码复杂性指标,包括我们到目前为止介绍的诸如Halstead,Cyclomatic和LOC之类的代码。 wily与Git集成,可以跨Git分支和修订版自动收集指标。

The purpose of wily is to give you the ability to see trends and changes in the complexity of your code over time. If you were trying to fine-tune a car or improve your fitness, you’d start off with measuring a baseline and tracking improvements over time.

wily的目的是使您能够查看代码复杂度随时间的趋势和变化。 如果您想对汽车进行微调或改善健康状况,那么首先要测量基线并随时间推移跟踪改进情况。

wily安装 (Installing wily)

wily is available on PyPi and can be installed using pip:

wily可用PyPI上 ,并可以使用PIP进行安装:

Once wily is installed, you have some commands available in your command-line:

一旦安装了wily ,您就可以在命令行中使用一些命令:

  • wily build: iterate through the Git history and analyze the metrics for each file
  • wily report: see the historical trend in metrics for a given file or folder
  • wily graph: graph a set of metrics in an HTML file
  • wily build遍历Git历史记录并分析每个文件的指标
  • wily report查看给定文件或文件夹的指标的历史趋势
  • wily graph在HTML文件中绘制一组指标

建立缓存 (Building a Cache)

Before you can use wily, you need to analyze your project. This is done using the wily build command.

在开始wily使用之前,您需要分析您的项目。 这是使用wily build命令完成的。

For this section of the tutorial, we will analyze the very popular requests package, used for talking to HTTP APIs. Because this project is open-source and available on GitHub, we can easily access and download a copy of the source code:

在本教程的这一部分中,我们将分析非常受欢迎的requests包,该requests包用于与HTTP API进行通信。 因为该项目是开源的,并且可以在GitHub上使用,所以我们可以轻松地访问和下载源代码的副本:

 $ git clone https://github.com/requests/requests
$ git clone https://github.com/requests/requests
$ $ cd requests
cd requests
$ ls
$ ls
AUTHORS.rst        CONTRIBUTING.md    LICENSE            Makefile
AUTHORS.rst        CONTRIBUTING.md    LICENSE            Makefile
Pipfile.lock       _appveyor          docs               pytest.ini
Pipfile.lock       _appveyor          docs               pytest.ini
setup.cfg          tests              CODE_OF_CONDUCT.md HISTORY.md
setup.cfg          tests              CODE_OF_CONDUCT.md HISTORY.md
MANIFEST.in        Pipfile            README.md          appveyor.yml
MANIFEST.in        Pipfile            README.md          appveyor.yml
ext                requests           setup.py           tox.ini
ext                requests           setup.py           tox.ini

Note: Windows users should use the PowerShell command prompt for the following examples instead of traditional MS-DOS Command-Line. To start the PowerShell CLI press Win+R and type powershell then Enter.

注意: Windows用户应将PowerShell命令提示符用于以下示例,而不是传统的MS-DOS命令行。 要启动PowerShell CLI,请按Win + R ,然后键入powershell然后按Enter 。

You will see a number of folders here, for tests, documentation, and configuration. We’re only interested in the source code for the requests Python package, which is in a folder called requests.

您将在此处看到许多文件夹,用于测试,文档和配置。 我们只对requests Python包的源代码感兴趣,该源代码位于一个名为requests的文件夹中。

Call the wily build command from the cloned source code and provide the name of the source code folder as the first argument:

从克隆的源代码中调用wily build命令,并提供源代码文件夹的名称作为第一个参数:

This will take a few minutes to analyze, depending on how much CPU power your computer has:

这将需要几分钟的时间进行分析,具体取决于计算机具有的CPU能力:

收集项目数据 (Collecting Data on Your Project)

Once you have analyzed the requests source code, you can query any file or folder to see key metrics. Earlier in the tutorial, we discussed the following:

在分析了requests源代码之后,您可以查询任何文件或文件夹以查看关键指标。 在本教程的前面,我们讨论了以下内容:

  • Lines of Code
  • Maintainability Index
  • Cyclomatic Complexity
  • 代码行
  • 维修性指数
  • 圈复杂度

Those are the 3 default metrics in wily. To see those metrics for a specific file (such as requests/api.py), run the following command:

这些是wily中的3个默认指标。 要查看特定文件(例如, requests/api.py )的那些指标, requests/api.py运行以下命令:

 $ wily report requests/api.py
$ wily report requests/api.py

wily will print a tabular report on the default metrics for each Git commit in reverse date order. You will see the most recent commit at the top and the oldest at the bottom:

wily将按照相反的日期顺序,以表格形式报告每个Git提交的默认指标。 您将在顶部看到最新的提交,在底部看到最旧的提交:

Revision 修订版 Author 作者 Date 日期 MI MI Lines of Code 代码行 Cyclomatic Complexity 圈复杂度
f37daf2 f37daf2 Nate Prewitt 内特·普雷维特 2019-01-13 2019-01-13 100 (0.0) 100(0.0) 158 (0) 158(0) 9 (0) 9(0)
6dd410f 6dd410f Ofek Lev 奥菲克·列夫 2019-01-13 2019-01-13 100 (0.0) 100(0.0) 158 (0) 158(0) 9 (0) 9(0)
5c1f72e 5c1f72e Nate Prewitt 内特·普雷维特 2018-12-14 2018-12-14 100 (0.0) 100(0.0) 158 (0) 158(0) 9 (0) 9(0)
c4d7680 c4d7680 Matthieu Moy 马修·莫伊(Matthieu Moy) 2018-12-14 2018-12-14 100 (0.0) 100(0.0) 158 (0) 158(0) 9 (0) 9(0)
c452e3b c452e3b Nate Prewitt 内特·普雷维特 2018-12-11 2018-12-11 100 (0.0) 100(0.0) 158 (0) 158(0) 9 (0) 9(0)
5a1e738 5a1e738 Nate Prewitt 内特·普雷维特 2018-12-10 2018-12-10 100 (0.0) 100(0.0) 158 (0) 158(0) 9 (0) 9(0)

This tells us that the requests/api.py file has:

这告诉我们requests/api.py文件具有:

  • 158 lines of code
  • A perfect maintainability index of 100
  • A cyclomatic complexity of 9
  • 158行代码
  • 完美的可维护性指数为100
  • 圈复杂度为9

To see other metrics, you first need to know the names of them. You can see this by running the following command:

要查看其他指标,您首先需要知道它们的名称。 您可以通过运行以下命令来查看此内容:

You will see a list of operators, modules that analyze the code, and the metrics they provide.

您将看到操作员,分析代码的模块及其提供的指标的列表。

To query alternative metrics on the report command, add their names after the filename. You can add as many metrics as you wish. Here’s an example with the Maintainability Rank and the Source Lines of Code:

要在报告命令上查询替代指标,请在文件名后添加其名称。 您可以根据需要添加任意多个指标。 这是带有可维护性等级和代码源代码行的示例:

 $ wily report requests/api.py maintainability.rank raw.sloc
$ wily report requests/api.py maintainability.rank raw.sloc

You will see the table now has 2 different columns with the alternative metrics.

您将看到该表现在具有2个不同的列以及备用指标。

图形指标 (Graphing Metrics)

Now that you know the names of the metrics and how to query them on the command line, you can also visualize them in graphs. wily supports HTML and interactive charts with a similar interface as the report command:

既然您知道了指标的名称以及如何在命令行上查询它们,您还可以在图形中可视化它们。 wily支持HTML和交互式图表,其界面类似于report命令:

Your default browser will open with an interactive chart like this:

您的默认浏览器将打开,并显示一个交互式图表,如下所示:

You can hover over specific data points, and it will show the Git commit message as well as the data.

您可以将鼠标悬停在特定的数据点上,它将显示Git提交消息以及数据。

If you want to save the HTML file in a folder or repository, you can add the -o flag with the path to a file:

如果要将HTML文件保存在文件夹或存储库中,可以在文件路径中添加-o标志:

 $ wily graph requests/sessions.py maintainability.mi -o my_report.html
$ wily graph requests/sessions.py maintainability.mi -o my_report.html

There will now be a file called my_report.html that you can share with others. This command is ideal for team dashboards.

现在将有一个名为my_report.html的文件,您可以与他人共享。 此命令是团队仪表板的理想选择。

wily作为一个pre-commit挂钩 (wily as a pre-commit Hook)

wily can be configured so that before you commit changes to your project, it can alert you to improvements or degradations in complexity.

可以对wily进行配置,以便在将更改提交到项目之前,它可以提醒您复杂性的提高或降低。

wily has a wily diff command, that compares the last indexed data with the current working copy of a file.

wily具有wily diff命令,该命令将最后索引的数据与文件的当前工作副本进行比较。

To run a wily diff command, provide the names of the files you have changed. For example, if I made some changes to requests/api.py you will see the impact on the metrics by running wily diff with the file path:

要运行wily diff命令,请提供您已更改的文件的名称。 例如,如果我对requests/api.py进行了一些更改,则通过使用文件路径requests/api.py wily diff运行,您将看到对指标的影响:

In the response, you will see all of the changed metrics, as well as the functions or classes that have changed for cyclomatic complexity:

在响应中,您将看到所有已更改的度量标准,以及因圈复杂性而更改的函数或类:

The diff command can be paired with a tool called pre-commit. pre-commit inserts a hook into your Git configuration that calls a script every time you run the git commit command.

diff命令可以与称为pre-commit的工具配对。 pre-commit在您的Git配置中插入一个钩子,每次运行git commit命令时都会调用一个脚本。

To install pre-commit, you can install from PyPI:

要安装pre-commit ,可以从PyPI安装:

 $ pip install pre-commit
$ pip install pre-commit

Add the following to a .pre-commit-config.yaml in your projects root directory:

将以下内容添加到项目根目录中的.pre-commit-config.yaml

Once setting this, you run the pre-commit install command to finalize things:

设置此选项后,您可以运行pre-commit install命令完成操作:

 $ pre-commit install
$ pre-commit install

Whenever you run the git commit command, it will call wily diff along with the list of files you’ve added to your staged changes.

每当您运行git commit命令时,它都会调用wily diff以及您已添加到暂存更改中的文件列表。

wily is a useful utility to baseline the complexity of your code and measure the improvements you make when you start to refactor.

wily是有用的实用程序,可用来确定代码的复杂性并评估您开始重构时所做的改进。

在Python中重构 (Refactoring in Python)

Refactoring is the technique of changing an application (either the code or the architecture) so that it behaves the same way on the outside, but internally has improved. These improvements can be stability, performance, or reduction in complexity.

重构是一种更改应用程序(代码或体系结构)的技术,以便它在外部具有相同的行为,但在内部已得到改进。 这些改进可以是稳定性,性能或复杂性的降低。

One of the world’s oldest underground railways, the London Underground, started in 1863 with the opening of the Metropolitan line. It had gas-lit wooden carriages hauled by steam locomotives. On the opening of the railway, it was fit for purpose. 1900 brought the invention of the electric railways.

世界上最古老的地下铁路之一,伦敦地铁(London Underground)于1863年随着大都会线的启用而开始运营。 它有被汽油机车拖着的汽油点燃的木制马车。 在铁路通车时,这是有目的的。 1900年,发明了电力铁路。

By 1908, the London Underground had expanded to 8 railways. During the Second World War, the London Underground stations were closed to trains and used as air-raid shelters. The modern London Underground carries millions of passengers a day with over 270 stations:

到1908年,伦敦地铁已扩展到8条铁路。 第二次世界大战期间,伦敦地铁站不对火车开放,并被用作防空洞。 现代化的伦敦地铁每天通过270多个车站运送数百万名乘客:

Joint London Underground Railways Map, c. 1908 (Image: Wikipedia)
联合伦敦地下铁路地图c。 1908年(图片来源: Wikipedia )

It’s almost impossible to write perfect code the first time, and requirements change frequently. If you would have asked the original designers of the railway to design a network fit for 10 million passengers a day in 2020, they would not design the network that exists today.

第一次编写完美的代码几乎是不可能的,而且需求经常变化。 如果您要让铁路的原始设计者设计一个到2020年每天可容纳1000万人次的网络,他们将不会设计如今存在的网络。

Instead, the railway has undergone a series of continuous changes to optimize its operation, design, and layout to match the changes in the city. It has been refactored.

取而代之的是,铁路经历了一系列连续的变化,以优化其运营,设计和布局,以适应城市的变化。 它已被重构。

In this section, you’ll explore how to safely refactor by leveraging tests and tools. You’ll also see how to use the refactoring functionality in Visual Studio Code and PyCharm:

在本节中,您将探索如何通过利用测试和工具来安全地重构。 您还将看到如何在Visual Studio Code和PyCharm中使用重构功能:

通过重构规避风险:利用工具并进行测试 (Avoiding Risks With Refactoring: Leveraging Tools and Having Tests)

If the point of refactoring is to improve the internals of an application without impacting the externals, how do you ensure the externals haven’t changed?

如果重构的重点是改善应用程序的内部结构而又不影响外部结构,那么如何确保外部结构没有变化?

Before you charge into a major refactoring project, you need to make sure you have a solid test suite for your application. Ideally, that test suite should be mostly automated, so that as you make changes, you see the impact on the user and address it quickly.

在进行大型重构项目之前,需要确保您的应用程序具有可靠的测试套件。 理想情况下,该测试套件应基本上是自动化的,以便在进行更改时,您可以看到对用户的影响并Swift解决。

If you want to learn more about testing in Python, Getting Started With Testing in Python is a great place to start.

如果您想了解有关使用Python测试的更多信息,那么使用 Python测试入门是一个不错的起点。

There is no perfect number of tests to have on your application. But, the more robust and thorough the test suite, the more aggressively you can refactor your code.

您的应用程序上没有完美的测试数量。 但是,测试套件越健壮和彻底,您可以更积极地重构代码。

The two most common tasks you will perform when doing refactoring are:

进行重构时,您将执行的两个最常见的任务是:

  • Renaming modules, functions, classes, and methods
  • Finding usages of functions, classes, and methods to see where they are called
  • 重命名模块,函数,类和方法
  • 查找函数,类和方法的用法以查看它们的调用位置

You can simply do this by hand using search and replace, but it is both time consuming and risky. Instead, there are some great tools to perform these tasks.

您可以简单地使用search和replace手动完成此操作,但这既耗时又冒险。 相反,有一些很棒的工具可以执行这些任务。

使用rope进行重构 (Using rope for Refactoring)

rope is a free Python utility for refactoring Python code. It comes with an extensive set of APIs for refactoring and renaming components in your Python codebase.

rope是用于重构Python代码的免费Python实用程序。 它带有丰富的API集,可用于重构和重命名Python代码库中的组件。

rope can be used in two ways:

rope有两种使用方式:

  1. By using an editor plugin, for Visual Studio Code, Emacs, or Vim
  2. Directly by writing scripts to refactor your application
  1. 通过使用用于Visual Studio Code , Emacs或Vim的编辑器插件
  2. 直接通过编写脚本来重构您的应用程序

To use rope as a library, first install rope by executing pip:

要将绳索用作库,请首先通过执行pip安装rope

It is useful to work with rope on the REPL so that you can explore the project and see changes in real time. To start, import the Project type and instantiate it with the path to the project:

在REPL上使用rope很有用,这样您就可以浏览项目并实时查看更改。 首先,导入Project类型并使用项目路径实例化它:

>>>

>>> from rope.base.project import Project>>> proj = Project('requests')

>>>

The proj variable can now perform a series of commands, like get_files and get_file, to get a specific file. Get the file api.py and assign it to a variable called api:

proj变量现在可以执行一系列命令,例如get_filesget_file ,以获取特定文件。 获取文件api.py并将其分配给名为api的变量:

>>>

>>> [f.name for f in proj.get_files()]
['structures.py', 'status_codes.py', ...,'api.py', 'cookies.py']>>> api = proj.get_file('api.py')

>>>

If you wanted to rename this file, you could simply rename it on the filesystem. However, any other Python files in your project that imported the old name would now be broken. Let’s rename the api.py to new_api.py:

如果要重命名该文件,则只需在文件系统上重命名即可。 但是,项目中导入了旧名称的任何其他Python文件现在都将被破坏。 让我们将api.py重命名为new_api.py

>>>

>>> from rope.refactor.rename import Rename>>> change = Rename(proj, api).get_changes('new_api')>>> proj.do(change)

>>>

Running git status, you will see that rope made some changes to the repository:

运行git status ,您将看到rope对存储库进行了一些更改:

 $ git status
$ git status
On branch master
On branch master
Your branch is up to date with 'origin/master'.Your branch is up to date with 'origin/master'.Changes not staged for commit:
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)  (use "git checkout -- <file>..." to discard changes in working directory)    modified:   requests/__init__.py
    modified:   requests/__init__.py
    deleted:    requests/api.py    deleted:    requests/api.pyUntracked files:
Untracked files:
  (use "git add <file>..." to include in what will be committed)  (use "git add <file>..." to include in what will be committed)   requests/.ropeproject/
   requests/.ropeproject/
   requests/new_api.py   requests/new_api.pyno changes added to commit (use "git add" and/or "git commit -a")
no changes added to commit (use "git add" and/or "git commit -a")

The three changes made by rope are the following:

rope进行的三个更改如下:

  1. Deleted requests/api.py and created requests/new_api.py
  2. Modified requests/__init__.py to import from new_api instead of api
  3. Created a project folder named .ropeproject
  1. 删除的requests/api.py和创建的requests/new_api.py
  2. 修改后的requests/__init__.pynew_api而不是api导入
  3. 创建一个名为.ropeproject的项目文件夹

To reset the change, run git reset.

要重置更改,请运行git reset

There are hundreds of other refactorings that can be done with rope.

rope还可以完成其他数百种重构 。

使用Visual Studio代码进行重构 (Using Visual Studio Code for Refactoring)

Visual Studio Code opens up a small subset of the refactoring commands available in rope through its own UI.

Visual Studio Code通过自己的UI打开了rope可用的重构命令的一小部分。

You can:

您可以:

  1. Extract variables from a statement
  2. Extract methods from a block of code
  3. Sort imports into a logical order
  1. 从语句中提取变量
  2. 从代码块中提取方法
  3. 将导入按逻辑顺序排序

Here is an example of using the Extract methods command from the command palette:

这是从命令选项板使用“提取方法”命令的示例:

使用PyCharm进行重构 (Using PyCharm for Refactoring)

If you use or are considering using PyCharm as a Python editor, it’s worth taking note of the powerful refactoring capabilities it has.

如果您正在使用PyCharm或正在考虑将PyCharm用作Python编辑器,则应注意其强大的重构功能。

You can access all the refactoring shortcuts with the Ctrl+T command on Windows and macOS. The shortcut to access refactoring in Linux is Ctrl+Shift+Alt+T.

您可以在Windows和macOS上使用Ctrl + T命令访问所有重构快捷方式。 在Linux中,访问重构的快捷方式是Ctrl + Shift + Alt + T。

查找函数和类的调用方和用法 (Finding Callers and Usages of Functions and Classes)

Before you remove a method or class or change the way it behaves, you’ll need to know what code depends on it. PyCharm can search for all usages of a method, function, or class within your project.

在删除方法或类或更改其行为方式之前,您需要知道哪些代码依赖于该方法或类。 PyCharm可以搜索项目中方法,函数或类的所有用法。

To access this feature, select a method, class, or variable by right-clicking and select Find Usages:

要访问此功能,请通过右键单击选择方法,类或变量,然后选择查找用法:

All of the code that uses your search criteria is shown in a panel at the bottom. You can double-click on any item to navigate directly to the line in question.

底部的面板中显示了使用搜索条件的所有代码。 您可以双击任何项目以直接导航到相关行。

使用PyCharm重构工具 (Using the PyCharm Refactoring Tools)

Some of the other refactoring commands include the ability to:

其他一些重构命令包括以下功能:

  • Extract methods, variables, and constants from existing code
  • Extract abstract classes from existing class signatures, including the ability to specify abstract methods
  • Rename practically anything, from a variable to a method, file, class, or module
  • 从现有代码中提取方法,变量和常量
  • 从现有的类签名中提取抽象类,包括指定抽象方法的能力
  • 重命名几乎所有内容,从变量到方法,文件,类或模块

Here is an example of renaming the same api.py module you renamed earlier using the rope module to new_api.py:

这是将您之前使用rope模块重命名的相同api.py模块重命名为new_api.py

The rename command is contextualized to the UI, which makes refactoring quick and simple. It has updated the imports automatically in __init__.py with the new module name.

重命名命令已在上下文中关联到UI,这使得重构既快速又简单。 它已使用新的模块名称自动更新了__init__.py的导入。

Another useful refactor is the Change Signature command. This can be used to add, remove, or rename arguments to a function or method. It will search for usages and update them for you:

另一个有用的重构是“更改签名”命令。 这可用于向函数或方法添加,删除或重命名参数。 它将搜索用法并为您更新它们:

You can set default values and also decide how the refactoring should handle the new arguments.

您可以设置默认值,还可以决定重构应如何处理新参数。

摘要 (Summary)

Refactoring is an important skill for any developer. As you’ve learned in this chapter, you aren’t alone. The tools and IDEs already come with powerful refactoring features to be able to make changes quickly.

重构对于任何开发人员来说都是一项重要技能。 正如您在本章中学到的那样,您并不孤单。 这些工具和IDE已经具有强大的重构功能,可以快速进行更改。

复杂性反模式 (Complexity Anti-Patterns)

Now that you know how complexity can be measured, how to measure it, and how to refactor your code, it’s time to learn 5 common anti-patterns that make code more complex than it need be:

既然您已经知道如何测量复杂度,如何测量复杂度以及如何重构代码,现在该学习5种常见的反模式,这些反模式会使代码变得比需要的复杂得多:

If you can master these patterns and know how to refactor them, you’ll soon be on track (pun intended) to a more maintainable Python application.

如果您能熟练掌握这些模式并知道如何重构它们,那么您很快就会步入正轨(按双关语意)到一个更易于维护的Python应用程序。

1.应该成为对象的功能 (1. Functions That Should Be Objects)

Python supports procedural programming using functions and also inheritable classes. Both are very powerful and should be applied to different problems.

Python支持使用函数以及可继承类进行 过程编程 。 两者都非常强大,应该应用于不同的问题。

Take this example of a module for working with images. The logic in the functions has been removed for brevity:

以用于处理图像的模块为例。 为简洁起见,已删除函数中的逻辑:

There are a few issues with this design:

此设计存在一些问题:

  1. It’s not clear if crop_image() and get_image_thumbnail() modify the original image variable or create new images. If you wanted to load an image then create both a cropped and thumbnail image, would you have to copy the instance first? You could read the source code in the functions, but you can’t rely on every developer doing this.

  2. You have to pass the image variable as an argument in every call to the image functions.

  1. 目前尚不清楚,如果crop_image()get_image_thumbnail()修改原始image的变量或创建新的图像。 如果要加载图像,然后创建裁剪图像和缩略图图像,是否需要先复制实例? 您可以阅读函数中的源代码,但不能依靠每个开发人员来执行此操作。

  2. 在每次调用图像函数时,都必须将image变量作为参数传递。

This is how the calling code might look:

这就是调用代码的外观:

 from from imagelib imagelib import import load_imageload_image , , crop_imagecrop_image , , get_image_thumbnailget_image_thumbnailimage image = = load_imageload_image (( '~/face.jpg''~/face.jpg' )
)
image image = = crop_imagecrop_image (( imageimage , , 400400 , , 500500 )
)
thumb thumb = = get_image_thumbnailget_image_thumbnail (( imageimage )
)

Here are some symptoms of code using functions that could be refactored into classes:

以下是使用可以重构为类的函数的代码的一些症状:

  • Similar arguments across functions
  • Higher number of Halstead h2 unique operands
  • Mix of mutable and immutable functions
  • Functions spread across multiple Python files
  • 跨函数的类似论点
  • Halstead h2 唯一操作数的数量更多
  • 可变和不变功能的混合
  • 函数分布在多个Python文件中

Here is a refactored version of those 3 functions, where the following happens:

这是这三个函数的重构版本,其中会发生以下情况:

  • .__init__() replaces load_image().
  • crop() becomes a class method.
  • get_image_thumbnail() becomes a property.
  • .__init__()替换load_image()
  • crop()成为一个类方法。
  • get_image_thumbnail()成为一个属性。

The thumbnail resolution has become a class property, so it can be changed globally or on that particular instance:

缩略图分辨率已成为类属性,因此可以在全局或特定实例上进行更改:

If there were many more image-related functions in this code, the refactoring to a class could make a drastic change. The next consideration would be the complexity of the consuming code.

如果此代码中还有更多与图像相关的功能,则对类的重构可能会带来巨大的变化。 下一个考虑因素是使用代码的复杂性。

This is how the refactored example would look:

重构后的示例如下所示:

 from from imagelib imagelib import import ImageImageimage image = = ImageImage (( '~/face.jpg''~/face.jpg' )
)
imageimage .. cropcrop (( 400400 , , 500500 )
)
thumb thumb = = imageimage .. thumbnail
thumbnail

In the resulting code, we have solved the original problems:

在生成的代码中,我们解决了原始问题:

  • It is clear that thumbnail returns a thumbnail since it is a property, and that it doesn’t modify the instance.
  • The code no longer requires creating new variables for the crop operation.
  • 显然, thumbnail是缩略图,因为它是一个属性,并且不会修改实例,因此它会返回缩略图。
  • 该代码不再需要为裁剪操作创建新变量。

2.应该是功能的对象 (2. Objects That Should Be Functions)

Sometimes, the reverse is true. There is object-oriented code which would be better suited to a simple function or two.

有时,情况恰恰相反。 有面向对象的代码更适合于一个或两个简单函数。

Here are some tell-tale signs of incorrect use of classes:

以下是一些不正确使用类的迹象:

  • Classes with 1 method (other than .__init__())
  • Classes that contain only static methods
  • 具有1个方法的类( .__init__()
  • 仅包含静态方法的类

Take this example of an authentication class:

以身份验证类的示例为例:

It would make more sense to just have a simple function named authenticate() that takes username and password as arguments:

拥有一个简单的名为authenticate()函数会更有意义,该函数将usernamepassword作为参数:

 # authenticate.py# authenticate.pydef def authenticateauthenticate (( usernameusername , , passwordpassword ):):......return return result
result

You don’t have to sit down and look for classes that match these criteria by hand: pylint comes with a rule that classes should have a minimum of 2 public methods. For more on PyLint and other code quality tools, you can check out Python Code Quality.

您不必坐下来手动寻找符合这些条件的类: pylint附带一个规则,即类至少应具有2个公共方法。 有关PyLint和其他代码质量工具的更多信息,可以查看Python代码质量 。

To install pylint, run the following command in your console:

要安装pylint ,请在控制台中运行以下命令:

pylint takes a number of optional arguments and then the path to one or more files and folders. If you run pylint with its default settings, it’s going to give a lot of output as pylint has a huge number of rules. Instead, you can run specific rules. The too-few-public-methods rule id is R0903. You can look this up on the documentation website:

pylint带有多个可选参数,然后带有一个或多个文件和文件夹的路径。 如果使用默认设置运行pylint ,由于pylint有大量规则,它将提供很多输出。 相反,您可以运行特定规则。 too-few-public-methods规则ID为R0903 。 您可以在文档网站上查找:

 $ pylint --disable$ pylint --disable =all --enable= all --enable =R0903 requests
= R0903 requests
************* Module requests.auth
************* Module requests.auth
requests/auth.py:72:0: R0903: Too few public methods (1/2) (too-few-public-methods)
requests/auth.py:72:0: R0903: Too few public methods (1/2) (too-few-public-methods)
requests/auth.py:100:0: R0903: Too few public methods (1/2) (too-few-public-methods)
requests/auth.py:100:0: R0903: Too few public methods (1/2) (too-few-public-methods)
************* Module requests.models
************* Module requests.models
requests/models.py:60:0: R0903: Too few public methods (1/2) (too-few-public-methods)requests/models.py:60:0: R0903: Too few public methods (1/2) (too-few-public-methods)-----------------------------------
-----------------------------------
Your code has been rated at 9.99/10
Your code has been rated at 9.99/10

This output tells us that auth.py contains 2 classes that have only 1 public method. Those classes are on lines 72 and 100. There is also a class on line 60 of models.py with only 1 public method.

此输出告诉我们auth.py包含2个仅具有1个公共方法的类。 这些类在第72行和第100行上。在models.py第60行上还有一个类,仅使用一种public方法。

3.将“三角”代码转换为平面代码 (3. Converting “Triangular” Code to Flat Code)

If you were to zoom out on your source code and tilt your head 90 degrees to the right, does the whitespace look flat like Holland or mountainous like the Himalayas? Mountainous code is a sign that your code contains a lot of nesting.

如果您要缩小源代码并将头向右倾斜90度,那么空白是像荷兰一样平坦还是像喜马拉雅山脉那样多山? 多山的代码表明您的代码包含很多嵌套。

Here’s one of the principles in the Zen of Python:

是Python Zen中的一项原则:

“Flat is better than nested”

“扁平比嵌套更好”

— Tim Peters, Zen of Python

— Tim Zens,Python的Zen

Why would flat code be better than nested code? Because nested code makes it harder to read and understand what is happening. The reader has to understand and memorize the conditions as they go through the branches.

为什么平面代码比嵌套代码更好? 因为嵌套代码使阅读和理解正在发生的事情变得更加困难。 读者必须理解并记住这些条件在分支中的经历。

These are the symptoms of highly nested code:

这些是高度嵌套的代码的症状:

  • A high cyclomatic complexity because of the number of code branches
  • A low Maintainability Index because of the high cyclomatic complexity relative to the number of lines of code
  • 由于代码分支的数量,循环复杂度很高
  • 相对于代码行数高的循环复杂性,可维护性指数低

Take this example that looks at the argument data for strings that match the word error. It first checks if the data argument is a list. Then, it iterates over each and checks if the item is a string. If it is a string and the value is "error", then it returns True. Otherwise, it returns False:

以这个示例为例,它查看与单词error匹配的字符串的参数data 。 它首先检查data参数是否为列表。 然后,遍历每个对象并检查该项目是否为字符串。 如果它是字符串并且值为"error" ,则返回True 。 否则,它返回False

This function would have a low maintainability index because it is small, but it has a high cyclomatic complexity.

由于该函数很小,因此它的可维护性指数很低,但是其复杂度很高。

Instead, we can refactor this function by “returning early” to remove a level of nesting and returning False if the value of data is not list. Then using .count() on the list object to count for instances of "error". The return value is then an evaluation that the .count() is greater than zero:

取而代之的是,我们可以通过“提前返回”以消除嵌套级别,并在data值未列出时返回False来重构此函数。 然后在列表对象上使用.count()来计算"error"实例。 然后,返回值是.count()大于零的评估:

 def def contains_errorscontains_errors (( datadata ):):if if not not isinstanceisinstance (( datadata , , listlist ):):return return FalseFalsereturn return datadata .. countcount (( "error""error" ) ) > > 0
0

Another technique for reducing nesting is to leverage list comprehensions. This common pattern of creating a new list, going through each item in a list to see if it matches a criterion, then adding all matches to the new list:

减少嵌套的另一种技术是利用列表理解。 创建新列表的常见模式是,遍历列表中的每个项目以查看其是否符合条件,然后将所有匹配项添加到新列表中:

This code can be replaced with a faster and more efficient list comprehension.

可以用更快,更有效的列表理解代替此代码。

Refactor the last example into a list comprehension and an if statement:

将最后一个示例重构为列表理解和if语句:

 results results = = [[ item item for for item item in in iterable iterable if if item item == == matchmatch ]
]

This new example is smaller, has less complexity, and is more performant.

这个新示例更小,更简单,更高效。

If your data is not a single dimension list, then you can leverage the itertools package in the standard library, which contains functions for creating iterators from data structures. You can use it for chaining iterables together, mapping structures, cycling or repeating over existing iterables.

如果数据不是单一维度列表,则可以利用标准库中的itertools包,该包包含用于从数据结构创建迭代器的功能。 您可以将其用于将可迭代对象链接在一起,映射结构,循环或在现有可迭代对象上重复。

Itertools also contains functions for filtering data, like filterfalse(). For more on Itertools, check out Itertools in Python 3, By Example.

Itertools还包含用于过滤数据的函数,例如filterfalse() 。 有关Itertools的更多信息,请查看Python 3中的Itertools示例 。

4.使用查询工具处理复杂的字典 (4. Handling Complex Dictionaries With Query Tools)

One of Python’s most powerful and widely used core types is the dictionary. It’s fast, efficient, scalable, and highly flexible.

字典是Python最强大和应用最广泛的核心类型之一。 它快速,高效,可扩展且高度灵活。

If you’re new to dictionaries, or think you could leverage them more, you can read Dictionaries in Python for more information.

如果您不熟悉字典,或者认为可以充分利用它们,则可以阅读Python词典以获取更多信息。

It does have one major side-effect: when dictionaries are highly nested, the code that queries them becomes nested too.

它确实有一个主要的副作用:当字典高度嵌套时,查询它们的代码也将嵌套。

Take this example piece of data, a sample of the Tokyo Metro lines you saw earlier:

以以下示例数据为例,这是您之前看到的Tokyo Metro线路的样本:

If you wanted to get the line that matched a certain number, this could be achieved in a small function:

如果您想获得与某个数字匹配的行,可以通过一个小的函数来实现:

 def def find_line_by_numberfind_line_by_number (( datadata , , numbernumber ):):matches matches = = [[ line line for for line line in in data data if if lineline [[ 'number''number' ] ] == == numbernumber ]]if if lenlen (( matchesmatches ) ) > > 00 ::return return matchesmatches [[ 00 ]]elseelse ::raise raise ValueErrorValueError (( ff "Line "Line  {number}{number}  does not exist." does not exist." )
)

Even though the function itself is small, calling the function is unnecessarily complicated because the data is so nested:

即使函数本身很小,由于数据如此嵌套,调用函数也不必要地变得复杂:

>>>
>>>

 >>>  find_line_by_number ( data [ "network" ][ "lines" ], 3 )

There are third party tools for querying dictionaries in Python. Some of the most popular are JMESPath, glom, asq, and flupy.

有第三方工具可在Python中查询字典。 最受欢迎的是JMESPath , glom , asq和flupy 。

JMESPath can help with our train network. JMESPath is a querying language designed for JSON, with a plugin available for Python that works with Python dictionaries. To install JMESPath, do the following:

JMESPath可以帮助我们的火车网络。 JMESPath是一种针对JSON设计的查询语言,具有适用于Python的可与Python词典一起使用的插件。 要安装JMESPath,请执行以下操作:

Then open up a Python REPL to explore the JMESPath API, copying in the data dictionary. To get started, import jmespath and call search() with a query string as the first argument and the data as the second. The query string "network.lines" means return data['network']['lines']:

然后打开Python REPL探索JMESPath API,并复制到data字典中。 首先,导入jmespath并使用查询字符串作为第一个参数并使用数据作为第二个调用search() 。 查询字符串"network.lines"表示返回data['network']['lines']

>>>

>>> import jmespath>>> jmespath.search("network.lines", data)
[{'name.en': 'Ginza', 'name.jp': '銀座線',
  'color': 'orange', 'number': 3, 'sign': 'G'},
 {'name.en': 'Marunouchi', 'name.jp': '丸ノ内線',
  'color': 'red', 'number': 4, 'sign': 'M'}]

>>>

When working with lists, you can use square brackets and provide a query inside. The “everything” query is simply *. You can then add the name of the attribute inside each matching item to return. If you wanted to get the line number for every line, you could do this:

使用列表时,可以使用方括号并在内部提供查询。 “一切”查询只是* 。 然后,您可以在每个匹配项中添加属性名称以返回。 如果要获取每行的行号,可以执行以下操作:

>>>

>>> jmespath.search("network.lines[*].number", data)
[3, 4]

>>>

You can provide more complex queries, like a == or <. The syntax is a little unusual for Python developers, so keep the documentation handy for reference.

您可以提供更复杂的查询,例如==< 。 语法对于Python开发人员来说有点不寻常,因此请妥善保存文档以供参考。

If we wanted to find the line with the number 3, this can be done in a single query:

如果我们想找到数字3的行,可以在单个查询中完成:

>>>

>>> jmespath.search("network.lines[?number==`3`]", data)
[{'name.en': 'Ginza', 'name.jp': '銀座線', 'color': 'orange', 'number': 3, 'sign': 'G'}]

>>>

If we wanted to get the color of that line, you could add the attribute in the end of the query:

如果我们想获取该行的颜色,则可以在查询的末尾添加属性:

>>>

>>> jmespath.search("network.lines[?number==`3`].color", data)
['orange']

>>>

JMESPath can be used to reduce and simplify code that queries and searches through complex dictionaries.

JMESPath可用于减少和简化通过复杂词典查询和搜索的代码。

5.使用attrsdataclasses减少代码 (5. Using attrs and dataclasses to Reduce Code)

Another goal when refactoring is to simply reduce the amount of code in the codebase while achieving the same behaviors. The techniques shown so far can go a long way to refactoring code into smaller and simpler modules.

重构的另一个目标是在实现相同行为的同时简单地减少代码库中的代码量。 到目前为止显示的技术在将代码重构为更小和更简单的模块方面可以走很长的路要走。

Some other techniques require a knowledge of the standard library and some third party libraries.

其他一些技术需要了解标准库和一些第三方库。

什么是样板? (What Is Boilerplate?)

Boilerplate code is code that has to be used in many places with little or no alterations.

样板代码是必须在很少或没有改动的情况下在许多地方使用的代码。

Taking our train network as an example, if we were to convert that into types using Python classes and Python 3 type hints, it might look something like this:

以我们的火车网络为例,如果我们要使用Python类和Python 3类型提示将其转换为类型,它可能看起来像这样:

 from from typing typing import import ListListclass class LineLine (( objectobject ):):def def __init____init__ (( selfself , , name_enname_en : : strstr , , name_jpname_jp : : strstr , , colorcolor : : strstr , , numbernumber : : intint , , signsign : : strstr ):):selfself .. name_en name_en = = name_enname_enselfself .. name_jp name_jp = = name_jpname_jpselfself .. color color = = colorcolorselfself .. number number = = numbernumberselfself .. sign sign = = signsigndef def __repr____repr__ (( selfself ):):return return ff "<Line "<Line  {self.name_en}{self.name_en}  color=' color=' {self.color}{self.color} ' number=' number= {self.number}{self.number}  sign=' sign=' {self.sign}{self.sign} '>"'>"def def __str____str__ (( selfself ):):return return ff "The "The  {self.name_en}{self.name_en}  line" line"class class NetworkNetwork (( objectobject ):):def def __init____init__ (( selfself , , lineslines : : ListList [[ LineLine ]):]):selfself .. _lines _lines = = lineslines@property@propertydef def lineslines (( selfself ) ) -> -> ListList [[ LineLine ]:]:return return selfself .. _lines
_lines

Now, you might also want to add other magic methods, like .__eq__(). This code is boilerplate. There’s no business logic or any other functionality here: we’re just copying data from one place to another.

现在,您可能还想添加其他魔术方法,例如.__eq__() 。 此代码是样板。 这里没有业务逻辑或任何其他功能:我们只是将数据从一个地方复制到另一个地方。

dataclasses案例 (A Case for dataclasses)

Introduced into the standard library in Python 3.7, with a backport package for Python 3.6 on PyPI, the dataclasses module can help remove a lot of boilerplate for these types of classes where you’re just storing data.

在python 3.7的标准库中引入了dataclasses模块,它在PyPI上具有用于python 3.6的backport包,可以帮助您删除这些仅用于存储数据的类类型的样板。

To convert the Line class above to a dataclass, convert all of the fields to class attributes and ensure they have type annotations:

要将上面的Line类转换为数据类,请将所有字段转换为类属性,并确保它们具有类型注释:

You can then create an instance of the Line type with the same arguments as before, with the same fields, and even .__str__(), .__repr__(), and .__eq__() are implemented:

然后,您可以使用与以前相同的参数和相同的字段创建Line类型的实例,甚至实现.__str__().__repr__().__eq__()

>>>

>>> line = Line('Marunouchi', "丸ノ内線", "red", 4, "M")>>> line.color
red>>> line2 = Line('Marunouchi', "丸ノ内線", "red", 4, "M")>>> line == line2
True

>>>

Dataclasses are a great way to reduce code with a single import that’s already available in the standard library. For a full walkthrough, you can checkout The Ultimate Guide to Data Classes in Python 3.7.

数据类是通过标准库中已经存在的单次导入来减少代码的好方法。 有关完整的演练,您可以查看Python 3.7中的《数据类终极指南》 。

一些attrs用例 (Some attrs Use Cases)

attrs is a third party package that’s been around a lot longer than dataclasses. attrs has a lot more functionality, and it’s available on Python 2.7 and 3.4+.

attrs是第三方软件包,比数据类的使用时间长得多。 attrs具有更多功能,并且可在Python 2.7和3.4+上使用。

If you are using Python 3.5 or below, attrs is a great alternative to dataclasses. Also, it provides many more features.

如果您使用Python 3.5或以下, attrs是一个伟大的替代dataclasses 。 此外,它还提供了更多功能。

The equivalent dataclasses example in attrs would look similar. Instead of using type annotations, the class attributes are assigned with a value from attrib(). This can take additional arguments, such as default values and callbacks for validating input:

attrs的等效数据类示例看起来类似。 不是使用类型注释,而是使用attrib()的值分配类属性。 这可以使用其他参数,例如默认值和用于验证输入的回调:

 from from attr attr import import attrsattrs , , attribattrib@attrs
@attrs
class class LineLine (( objectobject ):):name_en name_en = = attribattrib ()()name_jp name_jp = = attribattrib ()()color color = = attribattrib ()()number number = = attribattrib ()()sign sign = = attribattrib ()
()

attrs can be a useful package for removing boilerplate code and input validation on data classes.

attrs是删除模板代码和对数据类进行输入验证的有用软件包。

结论 (Conclusion)

Now that you’ve learned how to identify and tackle complicated code, think back to the steps you can now take to make your application easier to change and manage:

现在您已经学习了如何识别和处理复杂的代码,请回想一下现在可以采取的使您的应用程序更易于更改和管理的步骤:

  • Start off by creating a baseline of your project using a tool like wily.
  • Look at some of the metrics and start with the module that has the lowest maintainability index.
  • Refactor that module using the safety provided in tests and the knowledge of tools like PyCharm and rope.
  • 首先使用wily类的工具创建项目的基准。
  • 查看一些指标,然后从可维护性指数最低的模块开始。
  • 使用测试中提供的安全性以及PyCharm和rope等工具的知识来重构该模块。

Once you follow these steps and the best practices in this article, you can do other exciting things to your application, like adding new features and improving performance.

一旦遵循了这些步骤和本文中的最佳实践,您就可以对应用程序执行其他令人兴奋的事情,例如添加新功能和提高性能。

翻译自: https://www.pybloggers.com/2019/03/refactoring-python-applications-for-simplicity/

python重构

python重构_重构Python应用程序以简化操作相关推荐

  1. 第一章 第一节:Python基础_认识Python

    Python基础入门(全套保姆级教程) 第一章 第一节:Python基础_认识Python 1. 什么是编程 通俗易懂,编程就是用代码编写程序,编写程序有很多种办法,像c语言,javaPython语言 ...

  2. 【100天精通python】Day1:python入门_初识python,搭建python环境,运行第一个python小程序

    目录 专栏导读 1 初始python python 概述 python的应用领域 应用python的公司 2 搭建python 开发环境 2.1 安装python(以windows 系统为例)(1)下 ...

  3. hadoop调用python算法_使用Python实现Hadoop MapReduce程序

    根据上面两篇文章,下面是我在自己的ubuntu上的运行过程.文字基本采用博文使用Python实现Hadoop MapReduce程序,  打字很浪费时间滴. 在这个实例中,我将会向大家介绍如何使用Py ...

  4. 微信 小程序 python 渲染_干货 | 微信小程序自动化测试最佳实践(附 Python 源码)...

    本文为霍格沃兹测试学院测试大咖公开课<微信小程序自动化测试>图文整理精华版,进阶学习文末加群! 随着微信小程序的功能和生态日益完善,很多公司的产品业务形态逐渐从 App 延升到微信小程序. ...

  5. 微信小程序python自动化测试_干货 | 微信小程序自动化测试最佳实践(附 Python 源码)...

    本文为霍格沃兹测试学院测试大咖公开课<微信小程序自动化测试>图文整理精华版. 随着微信小程序的功能和生态日益完善,很多公司的产品业务形态逐渐从 App 延升到微信小程序.微信公众号等.小程 ...

  6. java python算法_用Python,Java和C ++示例解释的排序算法

    java python算法 什么是排序算法? (What is a Sorting Algorithm?) Sorting algorithms are a set of instructions t ...

  7. excel python插件_利用 Python 插件 xlwings 读写 Excel

    Python 通过 xlwings 读取 Excel 数据 去年底公司让我做设备管理,多次委婉拒绝,最终还是做了.其实我比较喜欢技术.做管理后发现现场没有停机率统计,而原始数据有,每次要自己在Exce ...

  8. 网络安全用python吗_使用Python进行网络安全渗透——密码攻击测试器

    相关文章: 本篇将会涉及: HTTP 基本认证 对HTTP Basic认证进行密码暴力攻击测试 什么是HTTP 基本认证 HTTP基本认证(HTTP Basic Authentication)是HTT ...

  9. 类的继承python事例_【Python五篇慢慢弹(5)】类的继承案例解析,python相关知识延伸...

    作者:白宁超 2016年10月10日22:36:57 摘要:继一文之后,笔者又将python官方文档认真学习下.官方给出的pythondoc入门资料包含了基本要点.本文是对文档常用核心要点进行梳理,简 ...

最新文章

  1. 为什么大多数IOC容器使用ApplicationContext,而不用BeanFactory
  2. [转]pragma comment的使用
  3. j2ee与mysql乱码过滤_mysql 在 j2ee中配置的乱码问题处理
  4. Mybatis分页插件更新
  5. bzoj1212: [HNOI2004]L语言
  6. 解决Windows安装TensorFlow报错:ERROR: Cannot uninstall 'wrapt'问题
  7. 可扩展架构设计的三个维度
  8. 京东面试官:你是怎么理解 MySQL 的优化原理的?
  9. 一些大厂的css reset 代码
  10. Blend4精选案例图解教程(二):找张图片玩特效
  11. Linux下编译安装Jsoncpp及应用实例
  12. 看故事也能长知识,CPU的工作原理原来这么简单!
  13. Cisco QOS之LLQ
  14. 步骤教学 :安装下载Oracle VM VirtualBox + 安装win7 win10镜像文件
  15. Fastadmin 自带的导入Excel功能
  16. 对于刚毕业的学弟学妹对即将要就业产生迷茫,下面我来为大家揭晓该如何选择!
  17. github这个项目,几行代码生成海报及二维码
  18. 知识图谱构建中的抽取方法
  19. 智能工厂建设整体解决方案
  20. APP移动端自动化测试工具选型“兵器谱”一览(主流开源工具)

热门文章

  1. 家居家纺行业的进销存软件怎么选择,门店管理系统选择
  2. DB2将DETACH下来的临时表ATTACH到分区表报错SQL20307N SQLSTATE=428GE
  3. 「利器x播客」访谈 006:播客不应该是把自己聊爽了放在第一位么?
  4. 当前人类社会面临的人工智能安全问题有哪些?
  5. 让自己网站对接google谷歌第三方登录接口详解说明
  6. 好玩的 css3 炫酷属性
  7. R语言包——tinytex的安装
  8. 通过HWND取得pid以及HANDLE
  9. 关联规则(一)Apriori算法
  10. Centos系统服务器挂载硬盘(ntfs格式和exfat格式)