Python3 - 开始python编程(十)

Python3 - 开始python编程(十)

上一篇文章中,我们介绍了与错误有关的所有内容,以帮助减少程序中的错误数量。今天,我们将更进一步,学习更多技术,以帮助您减少代码中的错误数量。

如果您像我希望的那样一直在练习,那么您可能已经编写了一个相当大的程序,吃过晚饭或出去了,而当您回来时,您会觉得代码有些失落。

开始编程时,会迷失很多事情。如果分心,您会感到沮丧,因为您忘记了五分钟前写的内容。不用担心对我们大多数人来说都是这样。我有好消息;随着时间的推移,它的确变得越来越容易。需要多长时间取决于您练习了多少。

对我来说,大约六个月的时间每天编写代码。从我醒来直到上床睡觉的时间,甚至在周末,我总是在写代码。不要误会我的意思;这并不意味着我没有生活。我仍然出去玩得很开心。有时我会筋疲力尽,不得不停止编写代码一两天。即使在那时,我仍在阅读文章,并努力提高自己的表现。

所以继续练习。


代码结构

Internet 上有很多指南可以告诉您如何编写代码,并且有有关如何格式化代码的文档,但是结构化代码始终留给开发人员。我并不是说“让我们成为标准”。那将是太多的工作,但是我们都可以同意您的代码有一个通用的起点,这就是我要讲的。由于我们还没有介绍模块或文件导入,所以我可以花些时间来做。

Imports

Imports 允许您导入位于其他文件或模块中的代码。例如,如果我有一个名为”models.py”的文件,里面有一个”Person”类,我需要在另一个名为”app.py”的文件中使用它,那么我不能只调用”Person”类,它将工作;我必须先导入它。那么我们该怎么做呢?

1
2
3
# app.py

import models

import 是 Python 中的独立关键字,它告诉解释器在其他地方查找代码,这些代码稍后将在此文件中调用。当解释器运行this文件中的代码时,这是处理的第一件事。在此示例中,我们导入整个`models’文件。我们只包含文件名;不是扩展名。

这使我们能够使用出现在models.py内部的每个类或函数。可以,但是有时我们想在导入内容中明确显示。取而代之,我们可以使用 from … import。

1
2
3
 app.py

from models import Person

在这里,我们有一个更好的导入声明版本。我们只导入Person类。如果要创建一个“狗”类,则需要为其单独导入,或者可以在“人”之后附加“狗”,并用逗号分隔。

1
2
3
# app.py

from models import Person, Dog

您还可以使用其他与您的代码无关的导入,您可能会发现它们很有趣。例如,如果您打开一个 Python 控制台并输入import this,您将在控制台窗口中看到 Tim Peters 的 The Python Zen。

这与本文有关,因此我将其粘贴在下面。

提姆·彼得斯(Tim Peters)撰写的《 Python 之禅》

美丽胜于丑陋。 显式优于隐式。 简单胜于复杂。 复杂胜于繁复。 扁平比嵌套更好。 稀疏胜于稠密。 可读性计数。 特殊情况不足以违反规则。 尽管实用性胜过纯度。 错误绝不能默默传递。 除非明确静音。 面对含糊不清的事物,拒绝猜测的诱惑。 应该有一种明显的方法,最好只有一种。 尽管除非您是荷兰人,否则一开始这种方式可能并不明显。 现在总比没有好。 尽管现在永远不会比现在的*right*好。 如果实施过程难以解释,那是个坏主意。 如果实现易于解释,则可能是个好主意。 命名空间是一个很棒的主意-让我们做更多这些吧!

请通读几次,并在编写代码时尽量记住这一点。

另一个有趣的是“反重力”。

Modules

模块只是包含相关代码(例如助手,服务甚至模型)的文件夹。

我意识到我从未真正解释过什么模型。模型主要是在程序运行时将用于存储数据的对象。人,狗,猫或地方可以视为模型。这些模型使您可以将特定对象的数据作为一个值而不是多个值来传递。

返回模块。模块和文件夹之间的结构差异是模块 ​​ 内部包含名为”init.py”的文件。该文件可以为空,或者可以为任何导入此模块的人提供初始配置信息。

1
2
3
4
5
6
7
my_project/
    models/
        __init__.py
        person.py
        dog.py
        cat.py
    app.py

使用上面的文件夹结构,我们可以使用以下语法导入 person 类:

1
2
3
4
5
import models.person                   # whole file

# or

from models.person import Person       # just the person class

我们可以调整init.py 使其包含 import 语句,以使其在其他任何地方都更容易使用。

1
2
3
4
5
6
7
8
9
10
# models/__init__.py

from models.person import Person
from models.dog import Dog
from models.cat import Cat

# or

from .person import Person
...

由于这些文件属于模块,因此我们可以使用。来表示当前文件夹,而无需键入 models。这对于小型项目来说很好,但是在大型项目中,了解从哪里导入可能更有意义,尤其是在您嵌套模块的情况下。

现在,当我们需要导入models文件夹内的任何类时,它将变得更加易于管理。

1
2
3
# app.py

from models import Person, Dog, Cat

看到那有多么容易?由于init.py,models 知道不同的类,我们可以直接导入这些类,而不必引用每个类存在的文件。


文件结构

好了,弄明白 import 后,我们开始学习代码结构,更具体地说是文件结构。

在编写程序时,我们需要确保我们的代码可读。我不了解您,但我倾向于从上至下,从左至右阅读。为什么我们的代码不应该这样做?

当我只能使用一个 Python 文件时,例如在编写脚本时,我希望它遵循以下方案:

1
2
3
4
5
6
7
8
9
[import statements]

[global variables/constants]

[class declarations]

[helper/one shot functions]

entry point

我有方括号,因为它们是可选的。您可能并不总是需要导入或其他任何项目。您的代码应该从某处开始,默认情况下,这是在顶级范围内。这就是为什么我们的print语句起作用的原因。

当需要首先调用函数时,标准约定是使用以下语法来启动程序。

1
2
if __name__ == "__main__":
    do_stuff()

有关更多信息,请查看此Stack Overflow 上的精彩文章

我按此特定顺序进行布局,因为它最有意义。

  1. 进口应始终处于所需范围之内;通常,这是文件的顶部。
  2. 接下来应该是全局变量和常量。打开代码并在顶部看到所有固定变量总是很高兴的;当其中之一需要更改时,它可以减少以后的搜索。
  3. 接下来是类声明,因为它们可能需要新的字段或在添加更多代码时进行引用。
  4. 一次性函数包含在上面的辅助函数中,但是它们通常位于辅助函数的上方,并且因为它们在程序运行时仅被调用一次而得名。这用于从环境变量进行状态配置,或者用于设置程序以使用其他源进行身份验证。
  5. 辅助函数是您会一遍又一遍使用的代码,例如生成随机数或对数字列表求和。
  6. 入口点是代码开始的地方。由于 C / C ++的年代久远,我们需要将所有声明放在顶部,因此我们将其放在底部。这只是出于习惯,您会经常看到这种情况。

照片由Glenn Carstens-PetersUnsplash上拍摄

原则

您的项目应该有意义,就好像您将东西放在整个房子的房间中一样。在编程中,我们没有沙发,烤箱或床,但有模型,服务,视图,助手,API 和应用程序。

Django 在分离功能方面做得很出色,迫使您使用适当的项目结构。

在进入项目结构之前,我想介绍一些原则。

首先,未损勿修,这应该是一个非常简单的原则。如果您编写的代码有效,即使您不知道它是如何工作的,也不要管它。如果您尝试修复它,可能会破坏它,却不知道如何将其恢复原状。

信息隐藏: 这仅仅是确保对可访问性(由访问控制定义)在您编写的每段代码上都尽可能严格。如果没有其他人可以看到”mysuper_secret_function()”,则不要将其公开。在前面添加一个”“,以便其他人知道不要使用它。如果您具有可以为您构建 URL 的联网代码,则让联网类创建 URL;在网络类中将该方法设置为 private。

松耦合: 这听起来很抽象,但事实并非如此。当您有一个类使用另一个类的一部分时,请仅从该类获得所需的内容;不要在您的原始类中实例化该类。松散耦合只是保持类之间的依赖性较低的结果。使用字典,列表或集合可以帮助减少每个类彼此之间的了解。


项目结构

您的项目结构将根据您需要执行的操作而变化。有时您只需要一个文件,但是当您需要更多文件时,如何跟踪所有文件?

这是我的典型文件结构,适用于中型到大型的仅 Python 应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
my_project/
  app/
    __init__.py
    main.py
    helper1.py
    helper2.pymodels/
    __init__.py
    class1.py
    class2.py
    class3.pyservices/
    __init__.py
    auth_service.py
    database_service.py
    network_service.pytests/
    __init__.py
    classes_test.py
    app_test.py
    services_test.pyvenv/
    ..app.py
  config.py
  requirements.txt
  test_requirements.txt
  .env
  .secrets

在这个项目中,我们有一个app模块,其中包含应用程序的所有业务逻辑。也就是将数据从一个地方移动到另一个地方并转换到另一个地方的代码。

main.py可以称为任何名称,但我只是为了使其易于理解。 “main.py”不是我们应用程序的入口,但是它是我们的程序开始工作的地方。 helper#.py文件甚至可能不是必需的,但是在这里我们仅出于示例而使用它们。

模型包含我们将在整个应用程序中使用的所有模型(即类)。

“服务”包含我们所有的背景代码。它的代码编写起来可能并不有趣,但是我们将需要多次使用它,而且它太大了,无法在一个文件中使用。在这里,我们有用于与服务器进行身份验证,连接到数据库并在数据库上执行 CRUD 操作(创建,读取,更新,删除)和联网代码的代码,因此我们不必继续编写代码来确保获得响应并返回。如果没有记录错误。

“tests”包含我们所有的测试。根据需要,测试文件可以是每个模块或文件,并且为了清楚起见,我们可以将它们分成模块。

venv 是我们尚未涉及的内容,但这是针对virtual environment的,我将在以后的教程中介绍。当您使用一个时,会自动为您创建一个,现在可以安全地忽略它。

app.py 是我们用来启动程序的文件。它通常包含一个导入,导入导入使仓鼠在轮子上运行的主文件。

config.py 是一个 Config 类,包含我们应用程序的默认运行配置。它不是必需的,但是很不错。

requirements.txttest_requirements.txt尚未涉及,但它们提供了运行此程序所需的外部模块及其版本号的列表。我喜欢将它们分开,所以我的生产代码中不包含任何测试模块。

.env 是*nix 系统上的一个隐藏文件,我们将创建该文件来存储环境变量。它绝对不是安全的,但是我们可以稍后对其进行解析,并根据需要提取值。

.secrets 与.env 相同,只不过它可用于存储用户名和密码。我强烈建议您仅在部署到 docker / podman 容器时才使用类似的方法,以便可以在将文件读入内存后将其删除。您还需要确保您的程序具有运行循环(例如网络服务器),以使其无法完成运行。一旦完成,所有这些变量将从内存中释放。

编码准则

Python 的开发人员创建了一些称为 PEP 的标准。最著名的是PEP 8 样式指南。很长的文档,但很有帮助。

一些亮点:

  • 行长不得超过 80 个字符。一些格式化程序(例如 Black)认为这已经过时,建议使用 88 个字符。
  • 如果必须超过 80 个字符,请始终以 120 个字符进行硬包装。
  • 建议需要多行的冗长公式在运算符(+-*/)之前中断。
  • 在顶级函数和类之间使用两条空行,在类中的方法之间使用一条空行,在函数内使用少量的空行来表示逻辑部分。
  • 从不同模块的导入应始终在单独的行上。
  • 应避免使用通配符导入(“从模型导入*”)。目前尚不清楚要导入什么。

有很多事情要做,所以我强烈建议您阅读。

要查看的另一个文档是PEP 257,其中涵盖了文档字符串。强烈建议您这样做,因为文档字符串通过提供每个函数的功能以及在支持文档字符串的编辑器(PyCharm,VS Code)中返回的内容的描述,使您的生活变得轻松。

使用描述性变量名

不要像人们在 C/C++中那样使用一个字母变量来混淆代码。多年来,”i”,”j”,”k”,”l”,”m”,”n”,”t”,”x”,”y”和”z”的值很多这么多东西他们可能意味着什么。告诉我,您有一个程序可以计算一段时间内以给定速度行进的距离,除了”j”,”k”和”m”外,什么都没有。那太好了,资深开发者,但是 k 是什么? 高级开发人员看了一下代码……啊,k是速度变量。

谁会知道 k 是速度变量,而不必考虑高级开发人员用来编写程序的公式?我们不是应该能够立即查看一段代码并说“是的,这是设置速度的地方;我可以在此处添加一个五分钟的休息时间来模拟停止,并以与高级开发人员告诉我”k”是相同的时间来完成。

关于单字母变量的一些历史:开发人员使用单字母变量的原因是不存在自动完成功能,并且开发人员不希望键入太多内容。它还确保程序员不会拼写错误的变量名。这相当于在 2000 年代初期在手机上发短信;我们使用lol,c u18r,<3 u,ttyl。为了缩短我们的单词,如果我们真正拼出完整的单词,我们只需要按一下按钮 20 次即可,而不是 100 次。时代变了;几乎每种主要的编程语言都具有自动完成功能。

有时候还可以;例如,if 语句中的临时变量,您仍可以在距键入位置五行的地方看到声明。这就像在 iPhone 上使用lol一样。一切都很好。但是,当您将”a”用作在整个类或结构中使用的变量时,那就很麻烦了。当我看到有人在智能手机上键入c u l8r时,我摇头。 但是您是否不必更改键盘类型以敲击 8 键?似乎需要更多的工作。与全局单字母变量相同,您必须弄清楚它的含义,然后再八个月后才能在更改中使用它。似乎需要付出更多的工作。

如果您忘了我在那儿所说的一切,请记住始终使用描述性变量和常量。我一直在没有自动完成的情况下从头开始编写这些教程,输入了一些很长的变量名,很少使用复制粘贴。为您的变量提供描述性名称with自动完成功能不应该成为问题。

摘要

今天,我们涵盖了一些要记住的原则,导入语句以从其他文件和模块引入代码,基本文件和项目结构以及 PEP 标准,从而为您在代码中应实现的目标提供了路线图。

我强烈建议您回顾一下先前的示例,并尝试使它们更漂亮。我将第一个告诉您,使代码清晰可能很困难。我的代码远不如我希望的那样整洁,但它可以工作。这是您越做越多的技能之一。

建议扫描

我说扫描不读书;阅读是理想的选择,但起初您不会记住一半。选择您要识别的重要部分并从此处开始。

PEP 8-Python 代码样式指南

PEP 257-Docstring 约定

还有其他 PEP,但如果有的话,我会留给您你想看看他们。


下一步是什么?

根据我的 Swift 文章,我们介绍了异步。我认为在进行异步编程之前,我们还有很多重要的事情要讲。虽然一次执行多项操作很有趣,但我认为如果我们先介绍虚拟环境和 PyPI 会更好。

尽管我提到我不会介绍外部模块,但我并未说我不会介绍如何获取它们,因此下一篇文章将介绍虚拟环境和获取 Python 包。

Rating: