22、Python 编程:脚本、模块、包与应用开发全解析

Python 编程:脚本、模块、包与应用开发全解析

1. 类与对象基础

在 Python 中,定义类和使用类的对象是编程的基础操作。类的行为由其方法来定义,而类的内部状态则是各种方法作用的结果。与其他一些语言不同,Python 并不需要正式声明实例变量,通常依靠 __init__() 方法为对象的状态提供初始或默认值。

Python 在解析属性和方法名时,会按照对象、类,然后是超类的顺序进行搜索。方法解析顺序基于类在初始类语句中的呈现顺序。

这里有两个重要的装饰器:
- @properties 装饰器:可用于创建具有与属性相同语法的方法,有助于简化复杂的算法。
- @staticmethod 装饰器:用于创建属于整个类且独立于类的任何特定实例的方法。

为了节省内存,可以使用 __slots__ 变量。它会构建一个不基于字典来存储属性的对象,这样的对象占用内存较小,但也存在一些局限性。另外,还可以创建可调用对象,这种对象可以像函数一样使用,同时具备对象的强大功能。

2. 脚本、模块、包和应用的概念

虽然在 Python 的交互式环境(REPL)中进行编程很方便,但我们的最终目标是创建 Python 应用文件。Python 文件可以分为以下几种类型:
- 脚本 :当由 Python 程序执行时,能够完成一些有用的工作。
- 模块 :设计用于被导入,以提供有用的定义。
- :包含 Python 模块的目录。

除了这些正式定义的概念,还有一些更通用的术语,如库、应用程序或框架,虽然语言没有对它们进行正式定义,但在 Python 中有实现这些概念的方法。例如,一组模块或包可以被视为一个库,Python 标准库就是一个大型的模块和包集合。一个“应用程序”至少包含一个脚本,更复杂的应用程序可能涉及一个脚本以及多个额外的模块和包。框架则是一个 Python 应用程序,我们可以将自定义的模块或包注入其中,许多框架还会包含非 Python 文件,如 Web 框架可能包含大量的 HTML 和 CSS,GUI 框架可能包含图像文件和字体。

3. 脚本文件规则

Python 脚本文件必须遵循一个简单的规则:必须是纯文本文件。此外,还有两条建议通常很有帮助:
- 内容 :必须是纯文本,理想情况下使用 UTF - 8 编码,不过 ASCII 编码也很常见。
- 文件名 :应遵循 Python 标识符规则,以字母开头,只使用字母、数字和下划线 _ 。以双下划线 __ 开头和结尾的文件名是保留的,对 Python 有特殊含义。
- 文件扩展名 :应该是 .py

这两条额外的建议对于编写模块和包是必不可少的,但对于编写简单脚本并非必需。脚本只是一系列语句,与在 REPL 提示符下执行的操作类似,唯一的区别是脚本没有隐式的打印输出,必须使用 print() 函数才能看到结果。在大型应用程序中,通常使用 logging 模块来生成更复杂的输出。随着应用程序的成熟,有时会将早期使用的 print() 函数替换为 logging.debug() 函数。

4. 运行脚本的方法

运行脚本需要将其作为输入提供给 Python 程序,常见的方法有以下三种:
- 按文件名运行 :这是最常见的方法,将文件名提供给 Python 命令即可。例如,假设在 Chapter_12 目录下有一个名为 ch12_script1.py 的文件,在 Linux 和 Mac OS X 中,完整的文件名是 Chapter_12/ch12_script1.py ,在 Windows 中是 Chapter_12\ch12_script1.py 。以下是在 Linux 系统上按文件名运行脚本的示例:

MacBookPro - SLott:Code slott$ python3 Chapter_12/ch12_script1.py
Temperature °C: 8
C = 8°, F = 46°

脚本文件 ch12_script1.py 的内容如下:

c = float(input("Temperature °C: "))
f = 32 + 9 * c / 5
print("C={c:.0f}°, F={f:.0f}°".format(c=c, f=f))
  • 按模块名运行 :在大多数情况下,可以将脚本安装到 Python 库的 site-packages 目录中,或者使用 PYTHONPATH 环境变量扩展 Python 路径,使脚本文件在 Python 的搜索路径中可见。
    • 安装到 site-packages 目录 :可以使用 Python 的 distutils 包,创建一个 setup.py 文件来描述要安装的模块,然后运行 python3 setup.py install 将模块安装到 site-packages 目录。像 pip easy - install 这样的安装程序也遵循这种标准模式。也可以手动找到 site-packages 目录并将模块复制到该目录,该目录在不同的操作系统中位置不同,它是 sys.path 变量的最后一项。
    • 设置 PYTHONPATH 环境变量 :在 Linux 中,可以使用 export 命令更改环境变量,通常将其放在 ~/.bash_profile 文件中。在 Windows 中,需要在高级系统设置中更改环境变量。通过设置 PYTHONPATH 变量,可以轻松创建包含多个模块的私有库。

一旦模块在 Python 的搜索路径中可见,就可以使用 -m 选项按模块名运行脚本。例如:

MacBookPro - SLott:Code slott$ python3 -m Chapter_12.ch12_script1
Temperature °C: 8
C = 8°, F = 46°
  • 使用操作系统 shell 规则运行 :在 Linux 和 Mac OS X 中,需要使脚本文件可执行,并在文件的第一行设置与 Python3 程序的关联。通常使用 #!/usr/bin/env python3 作为文件的第一行,这将使用操作系统的 env 程序来定位并启动 Python3 环境。使用 chmod +x 命令将文件标记为可执行,例如:
MacBookPro - SLott:Code slott$ chmod +x Chapter_12/ch12_script1.py

在 Windows 中,所有文件都被视为可执行的,文件扩展名与程序的关联通过 Windows 控制面板设置,在安装 Python 时就已经完成了设置。一旦文件被标记为可执行,就可以直接提供文件名来运行脚本。

5. 选择好的脚本名称

脚本名称应该简短且有意义,与文件名一样,最好避免使用复杂的前缀和后缀。Linux 或 Windows DOS 命令为脚本命名提供了一些参考,例如 git 命令,它使用简单的命令名作为前缀,通过不同的子命令来完成各种操作。 argparse 模块可以很好地支持这种方式,我们可以定义适用于所有子命令的通用参数,也可以定义每个子命令独有的参数。

6. 创建可重用模块

在 Python 中,模块是软件复用的基本单位。当某个功能需要在多个脚本中出现时,可以将该功能放在一个模块中,并在需要使用该功能的每个脚本中导入该模块。“复用”有两种不同的含义:
- 局部复用 :可以定义类层次结构,通过继承在应用程序内部实现局部复用,通常将相关的类定义在一个模块文件中。
- 跨应用复用 :定义一个模块,实现跨应用程序的复用。

要创建一个可导入的模块,只需确保 Python 文件位于 Python 搜索路径中的某个目录中。由于当前工作目录始终是可见的,因此可以通过在当前工作目录中创建文件来创建模块。

设计用于导入的模块主要由 import class def 语句组成,也可以使用赋值语句创建模块全局变量,但需要注意处理的工作量。通过赋值、类定义、函数定义或导入创建的任何名称都将位于该模块的命名空间中。

一个模块只会被导入一次,导入实现会检查已加载模块的全局缓存( sys.modules ),以确定模块是否已被导入。因此,一个实际进行某些处理的模块只会执行一次处理,之后的导入将被忽略,这使得在导入的模块中创建全局单例对象变得容易。不过,一般情况下,我们期望 import 语句提供类、函数和模块全局变量的定义,而不是进行有用的处理。

模块还可以定义独特的异常,例如在模块中创建一个名为 Error 的通用异常类:

class Error(Exception): pass

当导入该模块时,可以通过 some_module.Error 来引用这个异常:

import some_module
try:
    some_module.some_function()
except some_module.Error as e:
    logger.exception("some_function broke: {0}".format(e))
7. 创建混合库/应用模块

脚本通常会导入模块,可能会定义一些函数或类,并进行相关的处理。但这种简洁的脚本编写方式存在一些缺点,例如难以进行单元测试,因为每个单元测试都需要将脚本作为子进程调用,这可能会涉及大量的操作系统开销。而且,随着应用程序的成熟,一个好的脚本可能会成为更大、更全面的应用程序的一个组件,从脚本文件创建复合应用程序会变得困难,而从函数或类创建复合进程则要容易得多。

因此,建议采用以下脚本结构:

def c_to_f():
    c = float(input("Temperature °C: "))
    f = 32 + 9 * c / 5
    print("C={c:.0f}°, F={f:.0f}°".format(c=c, f=f))
if __name__ == "__main__":
    c_to_f()

通过将脚本封装在一个函数中,并使用 if __name__ == "__main__" 语句来区分主脚本和导入的模块。当模块被导入时,Python 将全局变量 __name__ 设置为实际的模块名称;当作为主脚本运行时,Python 将 __name__ 设置为 __main__

这种模式还可以用于编写运行自身单元测试的库模块,例如在一个从不作为主脚本使用的库模块中,可以包含以下代码:

if __name__ == "__main__":
    import doctest
    doctest.testmod(verbose=1)

这将运行嵌入在文档字符串中的所有单元测试。

8. 创建包

包是一个包含模块文件和一个额外文件的目录,每个包必须有一个 __init__.py 文件,该文件通常为空。

《Python 之禅》建议“扁平优于嵌套”,即尽可能将 Python 应用程序组织成扁平的模块集合,深度嵌套、复杂的包层次结构并不被认为是有益的。

可以通过两种方式使用包:
- 导入包中的模块 :例如,Python 标准库中的 XML 包包含多个 XML 解析器模块,可以使用 import xml.etree 导入 XML 包中的 etree 模块。
- 将包作为一个整体导入 :例如,当编写 import collections 时,实际上是在导入 collections/__init__.py 模块。

以下是一个简单的流程图,展示了 Python 脚本、模块和包的关系:

graph LR
    A[Python 应用] --> B[脚本]
    A --> C[模块]
    A --> D[包]
    C --> E[可重用功能]
    D --> F[模块文件]
    D --> G[__init__.py]

通过以上对 Python 脚本、模块、包和应用的详细介绍,我们可以更好地组织和开发 Python 程序,提高代码的复用性和可维护性。在实际编程中,根据具体需求选择合适的方式来创建和使用脚本、模块和包,能够让我们的开发工作更加高效。

Python 编程:脚本、模块、包与应用开发全解析

9. 不同类型文件的对比

为了更清晰地理解脚本、模块和包的区别,下面通过表格进行对比:
| 文件类型 | 定义 | 特点 | 示例 |
| ---- | ---- | ---- | ---- |
| 脚本 | 能在被 Python 程序执行时完成有用工作的文件 | 是一系列语句,无隐式打印输出,需用 print() 函数显示结果 | ch12_script1.py |
| 模块 | 设计用于被导入以提供有用定义的文件 | 主要由 import class def 语句组成,可实现软件复用 | 包含通用功能函数的模块文件 |
| 包 | 包含模块文件和 __init__.py 文件的目录 | 可组织多个相关模块,有扁平组织的建议 | XML 包 |

10. 运行脚本方法的选择

在实际开发中,选择合适的运行脚本方法很重要,以下是不同方法的适用场景:
- 按文件名运行 :适用于快速测试脚本,无需考虑模块导入和环境配置,直接执行脚本文件。
- 按模块名运行 :当脚本需要作为模块被其他程序导入,或者需要在不同环境中使用时,可将其安装到 site-packages 目录或设置 PYTHONPATH 环境变量,然后按模块名运行。
- 使用操作系统 shell 规则运行 :在需要将脚本作为可执行文件,像系统命令一样直接运行时使用,方便快捷。

下面是一个 mermaid 流程图,展示了选择运行脚本方法的决策过程:

graph TD
    A[开始] --> B{是否需要快速测试?}
    B -- 是 --> C[按文件名运行]
    B -- 否 --> D{是否需要作为模块导入?}
    D -- 是 --> E[按模块名运行]
    D -- 否 --> F{是否需要作为可执行文件运行?}
    F -- 是 --> G[使用操作系统 shell 规则运行]
    F -- 否 --> H[重新评估需求]
11. 模块复用的优势与实践

模块复用带来了诸多优势,如提高代码的可维护性和开发效率。以下是一些实践建议:
- 功能拆分 :将不同的功能拆分成独立的模块,每个模块负责单一的功能,便于管理和复用。
- 接口设计 :为模块设计清晰的接口,明确输入和输出,方便其他脚本或模块调用。
- 版本管理 :对模块进行版本管理,确保在不同项目中使用的模块版本一致。

12. 异常处理在模块中的应用

在模块中定义异常可以增强代码的健壮性。除了前面提到的创建通用异常类 Error ,还可以根据具体业务需求创建更细致的异常类。例如:

class InputError(Exception):
    """当输入不符合要求时抛出的异常"""
    pass

def divide_numbers(a, b):
    if b == 0:
        raise InputError("除数不能为零")
    return a / b

在调用该函数时,可以捕获并处理自定义异常:

try:
    result = divide_numbers(10, 0)
except InputError as e:
    print(f"发生错误: {e}")
13. 混合库/应用模块的扩展应用

混合库/应用模块的设计模式不仅可以用于单元测试,还可以用于其他场景。例如,在开发一个命令行工具时,可以根据不同的命令行参数执行不同的功能:

import argparse

def command_one():
    print("执行命令一")

def command_two():
    print("执行命令二")

def main():
    parser = argparse.ArgumentParser(description="命令行工具示例")
    subparsers = parser.add_subparsers(title="子命令", dest="command")

    parser_one = subparsers.add_parser("one", help="执行命令一")
    parser_two = subparsers.add_parser("two", help="执行命令二")

    args = parser.parse_args()

    if args.command == "one":
        command_one()
    elif args.command == "two":
        command_two()

if __name__ == "__main__":
    main()

在这个示例中,通过 argparse 模块解析命令行参数,根据不同的子命令调用不同的函数。

14. 包的组织与管理

在组织包时,要遵循扁平优于嵌套的原则。以下是一些包组织的建议:
- 功能分组 :将相关的模块放在同一个包中,便于管理和查找。
- 避免过度嵌套 :尽量减少包的嵌套层次,保持结构清晰。
- 文档编写 :为包和模块编写详细的文档,方便其他开发者使用。

15. 总结

通过对 Python 脚本、模块、包和应用的深入学习,我们了解了它们的概念、创建方法和使用场景。在实际开发中,合理运用脚本、模块和包可以提高代码的复用性、可维护性和开发效率。同时,掌握不同的运行脚本方法和异常处理技巧,能够让我们更好地应对各种开发需求。希望这些知识能够帮助你在 Python 编程的道路上更加得心应手。

以下是一个简单的列表,总结了本文的关键要点:
1. 掌握类与对象的基础,包括方法定义和属性解析。
2. 理解脚本、模块、包和应用的概念及区别。
3. 遵循脚本文件的规则,选择合适的文件名和扩展名。
4. 学会三种运行脚本的方法,并根据需求选择合适的方式。
5. 选择简短且有意义的脚本名称,使用 argparse 模块管理命令行参数。
6. 创建可重用模块,实现局部和跨应用的复用。
7. 运用混合库/应用模块的设计模式,方便单元测试和复合应用开发。
8. 合理组织包,遵循扁平优于嵌套的原则。

通过不断实践和探索,你将能够更加熟练地运用这些知识,开发出高质量的 Python 程序。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值