10 KiB
Python 包:通过捆绑模块来构建代码
我们使用 Python 包来构建和组织我们的代码。当谈到 Python 包时,人们通常指以下之一:
- 可以用 pip 和 pipenv 等工具安装的包通常通过 Python 包索引分发。
- 代码库中的包用于构造和组织代码。
如果你正在寻找如何安装软件包,你应该阅读关于用 pip install 安装软件包的文章。另外,我还可以推荐关于虚拟环境的文章。
这篇文章是关于创建你自己的包和模块。我们将看看什么是包,它们是如何构造的,以及如何创建 Python 包。您还将发现包和模块如何协同工作来组织和构建您的代码库。
如果您对模块不熟悉,请先阅读我关于 Python 模块的文章,然后再回到这里。这两个主题密切相关。
目录
- 什么是 Python 包?
- Python 包的结构
- 一个示例 Python 包
- 在 init 中导入模块。py
- 使用 main 创建一个可运行的包。py
- 模块与软件包
什么是 Python 包?
Python 包是包含零个或多个 Python 模块的目录。Python 包可以包含子包,子包也是包含模块的目录。每个包总是包含一个名为__init__.py的特殊文件。您将了解这个神秘文件的确切用途,以及如何使用它使您的包更容易从导入。
Python 包的结构
所以 Python 包是一个包含 Python 模块和一个__init__.py文件的文件夹。带有两个模块的简单 Python 包的结构如下:
.
└── package_name
├── __init__.py
├── module1.py
└── module2.py
如上所述,包可以包含子包。我们可以使用子包来进一步组织我们的代码。我将在下面的一节中更详细地向您展示如何做到这一点。让我们首先来看一下带有子包的包的结构:
.
└── package_name
├── __init__.py
├── subpackage1
├── __init__.py
├── module1.py
└── subpackage2
├── __init__.py
├── module2.py
如您所见,包是分层的,就像目录一样。
什么是 init。Python 包里的 py?
__init__.py文件是一个特殊的文件,总是在导入包时执行。用import package_name从上面导入包时,执行__init__.py文件。
从上面导入嵌套包时,用import package_name.subpackage1执行package_name和subpackage1的__init__.py文件。顺序如下:
- 首先执行
package_name的__init__.py文件, - 然后是
subpackage1的__init__.py文件。
我在所有的__init__.py文件中添加了简单的打印语句来演示。我们可以在package_name文件夹下创建一个main.py文件,内容如下:
import package_name.subpackage1
如果我们运行这个程序,输出将是:
$ python3 main.py
Hello from package_name
Hello from subpackage1
由于我们的子包的导入,两个__init__.py文件中的打印语句都被执行。
在 Python 包中组织您的代码
我们现在有了以下工具来正确组织我们的 Python 代码:
- 包装
- 子包
- 模块
您应该使用子包将相关的模块组合在一起。使用子包也有助于保持包和模块名称的简洁。当您发现自己在包名中使用下划线时,它们通常是一个不错的选择。
一个示例 Python 包
让我们在当前目录下创建一个名为httptools的包。我们的目标是,有一天,这个想象中的包将包含使用 HTTP 协议可能需要的所有工具。不过,我们从简单开始,只有 HTTP GET 和 POST 请求以及一个简单的 HTTP 服务器。
Thank you for reading my tutorials. I write these in my free time, and it requires a lot of time and effort. I use ads to keep writing these free articles, I hope you understand! Support me by disabling your adblocker on my website or, alternatively, buy me some coffee. It's much appreciated and allows me to keep working on this site!
我们的包包含两个子包:client和server。初始包布局可能如下所示:
httptools/
__init__.py
client/
__init__.py
get.py
post.py
server/
__init__.py
run.py
一些需要注意的事项:
- 这些名称简短且具有描述性。因为
client和server是httptools的子包,所以大家都很明显这些是一个 HTTP 客户端和服务器。我们不需要称他们为http_client和http_server。 - 我们将相似的功能分组到子包中:
- 客户端包中的客户端代码,
- 和服务器代码放在服务器包中。
- 我们已经将密切相关的代码分组到模块中。例如,我们需要做的所有 HTTP get 请求都放在
get.py模块中。
扩展我们的 Python 包
现在假设我们想跳到异步编程模型上。虽然我们最初的服务器曾经是同步的,但现在我们也提供了异步版本。
有三种方法可以将它添加到我们的代码库中:
- 创建一个名为
httptools_async的新包 - 在
http包中创建名为sync和async的新子包 - 创建新模块,命名为
async_get和async_post以及 async_run。
你会选择哪个选项?
选项 1:创建新的包
选项 1 是最简单的。我们可以创建一个名为httptools_async的新包,并将http包的内容复制到其中。接下来,我们将所有代码改为异步。我们原始包的用户可能只需修改一行代码就可以了:将import http改为import http_async。由于异步编程是一种完全不同的范式,这种简单的交换可能还不够。
选项 2:创建新的子包
选项二意味着现有的库用户,即使是那些不想使用 async 的用户,也需要修改他们的代码。重新开始时,这可能是一个好的选择,但对于现有的库来说,这不是一个好的选择。
选项 3:创建新模块
还有第三种选择:创建新的模块,在模块名后面附加 async。尽管现有用户不需要更改他们的代码,但我不推荐这样做。更合理的做法是将异步模块捆绑在它们自己的包中,因为你不必在导入时一直重复使用前缀/后缀。它符合保持包名和模块名简洁的总体目标。
在 init 中导入模块。py
通过导入__init__.py中的重要函数,我们的软件包可以得到更大的改进。__init__.py文件通常是导入其他模块的好地方。让我们以上面的httptools.client包为例。我们的 get.py 模块提供了一个返回响应的函数get()。我们可以将这个函数导入到client包的__init__.py文件中:
from httptools.client.get import get
这是 Python 中的一种常见模式。在__init__.py文件中导入所有需要的模块是一个好主意。如果我们使用如上所述的导入,我们包的用户可以像这样导入 get 函数:
from httptools.client import get
根据情况,也根据口味,你现在也可以这样做:
from httptools import client
client.get(...)
如果没有__init__.py中的导入,我们包的用户将需要像我们自己一样使用 get 函数:
from http.client.get import get
这对我们来说既冗长又缺乏灵活性。如果我们决定将 get 函数转移到其他文件,这将为我们代码的其他部分或我们包的用户引入一个突破性的变化。因为我们在 init 中导入了函数。但是,我们可以灵活地移动这个函数,只需简单地更改 init.py 中的导入,对于我们包的用户来说,这是不会被注意到的。
绝对或相对进口
我们已经使用绝对导入从httptools.client.get模块中导入了get()函数,这意味着我们指定了指向该函数的完整树。也可以使用相对导入。相对导入的好处是可以在不知道完整路径的情况下导入想要使用的模块。在不破坏代码的情况下,包名甚至可以改变。所以相对导入使你的代码对变化更健壮。
httptools.client的__init__.py文件中的相对导入如下所示:
from .get import get
这也是通配符有用的情况之一。我们可以使用通配符导入httptools.client.get模块中的所有元素:
from .get import *
这并不坏,因为我们确切地知道我们在get.py中放了什么。另外,我们可以在get.py中改变函数名,而不需要改变 import 语句。这也使得代码更加灵活。
使用 main 创建一个可运行的包。py
在模块一课中,我向您展示了如何创建可运行模块。我们可以通过创建一个名为__main__.py的文件来对包做类似的事情。
如何运行包中的模块
要从包中运行模块,我们可以使用以下 Python 命令:
python -m <module name>
此命令可用于运行您的包内的特定模块,例如python -m mypackage.mymodule,但我们也可以使用它来运行包。
创建可运行的包
要创建一个可运行的包,它需要在其根文件夹中有一个文件名__main__.py。这个文件可以导入和引导你喜欢的任何东西。注意,我们不需要添加if __name__=='__main__'检查:我们可以 100%确定这个模块的名字实际上是__main__,因为我们是这样命名这个文件的。
如果您的模块名为mymodule,您现在可以用下面的命令运行它:
python -m mymodule
模块与软件包
让我们总结一下关于 Python 包的信息以及我们在上一篇关于 Python 模块的文章中学到的知识。经常弹出的一个问题是这样的:模块和包有什么区别?
- 一个模块总是一个文件,而一个包可以包含许多模块。
- 模块用于捆绑 Python 函数、类、常量和任何其他您想重用的东西。反过来,一个包捆绑了模块。
- 模块可以独立存在,不需要成为包的一部分,而包需要模块才有用。
- 包和模块一起构成了组织代码的强大方式。