创建 Python 包

Beginner

This tutorial is from open-source community. Access the source code

简介

在这个实验中,你将学习如何创建和组织一个 Python 包。Python 包是一种很好的代码组织方式,它能让代码更具模块化、可复用性和可维护性。

本实验的目标是让你理解什么是 Python 包,创建一个基本的包结构,将相关的 Python 模块组织成一个连贯的包,并更新导入语句以适应新的包结构。

这是一个实验(Guided Lab),提供逐步指导来帮助你学习和实践。请仔细按照说明完成每个步骤,获得实际操作经验。根据历史数据,这是一个 中级 级别的实验,完成率为 67%。获得了学习者 100% 的好评率。

理解 Python 包

在开始创建 Python 包之前,让我们先了解一下什么是 Python 包。Python 包本质上是一个目录。在这个目录中,有多个 Python 模块文件,也就是包含 Python 代码的 .py 文件。此外,还有一个名为 __init__.py 的特殊文件。这个文件可以为空,但它的存在表明该目录是一个 Python 包。这种结构的目的是帮助你将相关代码组织到一个单一的目录层次结构中。

包有几个优点。首先,它们允许你对代码进行逻辑组织。你可以将相关功能组合在一个包中,而不是让所有 Python 文件分散在各处。其次,它们有助于避免模块之间的命名冲突。由于包创建了一个命名空间,你可以在不同的包中使用同名的模块而不会有任何问题。第三,它们使导入和使用你的代码更加方便。你可以轻松地导入整个包或其中的特定模块。

现在,让我们看看项目目录中目前有的文件。要列出这些文件,我们将在终端中使用以下命令:

ls -l

当你运行这个命令时,你应该会看到以下文件:

portfolio.csv
reader.py
stock.py
structure.py
tableformat.py
validate.py

这些 Python 文件都是相关的,并且可以协同工作,但目前它们只是独立的模块。在这个实验中,我们的目标是将它们组织成一个名为 structly 的连贯包。

让我们简要了解一下每个文件的作用:

  • structure.py:这个文件定义了一个基类 Structure 和各种描述符。这些描述符用于类型验证,即它们有助于确保程序中使用的数据具有正确的类型。
  • validate.py:它包含 structure 模块使用的验证功能。这有助于根据特定规则验证数据。
  • reader.py:这个文件提供了用于读取 CSV 数据的函数。CSV(逗号分隔值)是一种常见的用于存储表格数据的文件格式。
  • tableformat.py:它包含用于将数据格式化为表格的类和函数。当你想以更有条理的方式显示数据时,这很有用。
  • stock.py:这个文件使用其他模块来定义 Stock 类并处理股票数据。它结合了其他模块的功能来执行与股票数据相关的特定任务。

下一步,我们将创建包结构。

创建包结构

现在,我们要创建 Python 包了。但首先,让我们了解一下什么是 Python 包。Python 包是一种将相关 Python 模块组织到单个目录层次结构中的方式。它有助于更有效地管理和复用代码。要创建 Python 包,我们需要遵循以下步骤:

  1. 创建一个以包名命名的目录。这个目录将作为属于该包的所有模块的容器。
  2. 在这个目录中创建一个 __init__.py 文件。这个文件至关重要,因为它能让 Python 将该目录识别为一个包。当你导入这个包时,__init__.py 中的代码会被执行,可用于初始化该包。
  3. 将我们的 Python 模块文件移动到这个目录中。这一步确保所有相关代码都被分组在包内。

让我们逐步创建包结构:

  1. 首先,创建一个名为 structly 的目录。这将是我们包的根目录。
mkdir structly
  1. 接下来,在 structly 目录中创建一个空的 __init__.py 文件。
touch structly/__init__.py

__init__.py 文件可以为空,但要让 Python 将该目录视为一个包,它是必需的。当你导入这个包时,__init__.py 中的代码会被执行,可用于初始化该包。

  1. 现在,让我们把 Python 模块文件移动到 structly 目录中。这些模块文件包含了我们想要包含在包中的函数和类。
mv structure.py validate.py reader.py tableformat.py structly/
  1. 验证所有文件是否已正确移动。我们可以使用 ls -l 命令列出 structly 目录的内容。
ls -l structly/

你应该会看到列出以下文件:

__init__.py
reader.py
structure.py
tableformat.py
validate.py

现在我们已经创建了一个基本的包结构。目录层次结构应该如下所示:

project/
├── portfolio.csv
├── stock.py
└── structly/
    ├── __init__.py
    ├── reader.py
    ├── structure.py
    ├── tableformat.py
    └── validate.py

下一步,我们将修正导入语句,以使包能正常工作。

修复导入语句

现在,让我们来理解为什么需要这样做。当我们把文件移入 structly 包后,Python 查找模块的方式发生了变化。每个文件中的导入语句需要更新以匹配新的包结构。这一点至关重要,因为 Python 使用这些导入语句来查找和使用其他模块的代码。

structure.py 文件非常重要,需要更新。它从 validate.py 文件导入函数和类。由于这两个文件现在都在同一个 structly 包中,我们需要相应地调整导入语句。

让我们开始在编辑器中打开 structly/structure.py 文件。你可以点击文件浏览器中的 structly/structure.py,或者在终端中运行以下命令:

## 点击文件浏览器中的 structly/structure.py 或运行:
code structly/structure.py

文件打开后,查看导入语句的第一行。它目前看起来是这样的:

from validate import validate_type

当文件处于不同结构时,这个语句是正确的。但现在,我们需要更改它,以告知 Python 在同一包内查找 validate 模块。所以,我们将其更改为:

from .validate import validate_type

validate 前面的点 (.) 是这里的关键部分。这是 Python 中一种称为相对导入的特殊语法。它告诉 Python 在当前模块(在本例中是 structure.py)的同一包中搜索 validate 模块。

进行此更改后,请务必保存文件。保存很重要,因为它会使更改永久生效,并且当你运行代码时,Python 将使用更新后的导入语句。

现在,让我们检查其他文件,看看它们是否需要任何更新。

  1. structly/reader.py - 此文件不从我们的任何自定义模块导入。这意味着我们无需对其进行任何更改。
  2. structly/tableformat.py - 与 reader.py 文件类似,此文件也不从我们的任何自定义模块导入。因此,这里也不需要进行任何更改。
  3. structly/validate.py - 与前两个文件一样,它不从我们的任何自定义模块导入。因此,我们无需修改它。

在实际编程中,你的项目模块之间可能存在更复杂的关系。当你移动文件以创建或修改包结构时,请始终记住更新导入语句。这可以确保你的代码能够正确地找到和使用必要的模块。

更新并测试 stock.py 程序

既然我们已经创建了包并修正了内部导入语句,现在是时候更新 stock.py 文件以使用新的包结构了。在 Python 中,包是一种将相关模块组织在一起的方式。它有助于保持代码库的条理性,使代码更易于管理和复用。

在编辑器中打开 stock.py 文件:

## 在文件资源管理器中点击 stock.py 或运行:
code stock.py

目前 stock.py 中的导入语句是基于旧结构的,在旧结构中所有文件都位于同一目录下。在 Python 中,当你导入一个模块时,Python 会在特定位置查找该模块。在旧结构中,由于所有文件都在同一目录下,Python 可以轻松找到这些模块。但现在,有了新的包结构,我们需要更新导入语句,告诉 Python 在 structly 包中何处可以找到这些模块。

stock.py 文件更新为如下内容:

## stock.py

from structly.structure import Structure, String, PositiveInteger, PositiveFloat

class Stock(Structure):
    name = String()
    shares = PositiveInteger()
    price = PositiveFloat()

    @property
    def cost(self):
        return self.shares * self.price

    def sell(self, nshares: PositiveInteger):
        self.shares -= nshares

if __name__ == '__main__':
    from structly.reader import read_csv_as_instances
    from structly.tableformat import create_formatter, print_table
    portfolio = read_csv_as_instances('portfolio.csv', Stock)
    formatter = create_formatter('text')
    print_table(portfolio, ['name','shares','price'], formatter)

主要的更改如下:

  1. from structure import Structure, String, PositiveInteger, PositiveFloat 改为 from structly.structure import Structure, String, PositiveInteger, PositiveFloat。这一更改告诉 Python 在 structly 包中查找 structure 模块。
  2. from reader import read_csv_as_instances 改为 from structly.reader import read_csv_as_instances。同样,这一更改指示 Python 在 structly 包中查找 reader 模块。
  3. from tableformat import create_formatter, print_table 改为 from structly.tableformat import create_formatter, print_table。这确保 Python 在 structly 包中找到 tableformat 模块。

做出这些更改后保存文件。保存文件很重要,因为这样可以确保你所做的更改被保存下来,并且在运行程序时可以使用这些更改。

现在,让我们测试更新后的代码,确保一切正常运行:

python stock.py

你应该会看到以下输出:

      name      shares       price
---------- ---------- ----------
      MSFT        100      51.23
       IBM         50       91.1
      AAPL         75     145.89
      ACME        125     123.45
       HPE         75       32.2

如果你看到了这个输出,恭喜你!你已经成功创建了一个 Python 包,并更新了代码以使用它。这意味着你的代码现在以更模块化的方式进行了组织,便于未来的维护和扩展。

总结

在本次实验中,你学习了如何创建和构建 Python 包。具体而言,你理解了 Python 包在代码组织方面的概念和用途,创建了基本的包结构,将 Python 模块移入其中,更新了导入语句,并修改代码以正确使用该包。

这些技能对于开发大型 Python 应用程序至关重要,因为随着项目规模增大,合理的代码组织变得越来越重要。Python 包有助于将相关代码组织在一起,避免命名冲突,并使你的代码更具可复用性、可维护性和可共享性。在你继续 Python 学习之旅时,结构良好的包是专业 Python 开发的基石。