1. 概述
模块化编程是指将大型、繁杂的编程任务拆分为更小的、各自独立的、更易于管理的子任务或模块的过程。然后可以将各个模块拼凑在一起以创建更大的应用程序。
应用程序中模块化代码有几个优点:
-
简单性:一个模块通常只关注问题的一小部分。这使得开发更容易,更不容易出错。
-
可维护性:模块化设计为任务强制划分了逻辑边界。如果模块之间以最小化相互依赖的方式编写,那么对单个模块的修改对程序其他部分产生影响的可能性就会降低。(甚至可以在不了解模块外的应用程序的情况下对模块进行更改。)
-
可重用性:在单个模块中定义的功能可以被应用程序的其他部分轻松重用(通过适当定义的接口)。这消除了重复代码的需要。
-
范围:模块通常定义一个单独的命名空间,这样可以避免程序不同作用域中变量之间的冲突。
Python 中实现代码模块化的结构包括函数、模块和包, 本文介绍其中的模块和包.
1. 模块和包
1.1 模块-module
Python.org中定义模块如下
用作 Python 代码组织单元的对象。模块有一个包含任意 Python 对象的命名空间。模块通过import过程加载到 Python 中。
事实上实践中创建一个模块是非常简单的, 一个以 .py 结尾的文件就是一个模块.模块中的变量, 函数和类都可以通过 import 语句被加载到其他模块来使用.
新建一个 module.py 文件, 内容如下, 我们定义了2个变量 var 和 array . 定义了1个函数func和1个类Demo.
var = "right."
array = [10, 20, 30]
def func(arg):
print(f'arg = {arg}')
class Demo:
pass
然后在当前目录打开Python交互式命令行.
In [1]: import module
In [2]: module.var
Out[2]: 'right.'
In [3]: module.array
Out[3]: [10, 20, 30]
In [4]: module.func('data')
arg = data
In [5]: demo = module.Demo()
In [6]: demo
Out[6]: <module.Demo at 0x1ec5e0a81c8>
通过 import module 将module模块加载到当前作用域, 可以通过 module. 语法来使用module中的变量和函数和类.
1.2 包-package
Python.org 词汇表定义包如下:
可以包含子模块或递归子包的 Python 模块。从技术上讲,包是具有
__path__
属性的 Python 模块。
包提供了更高层次的组织代码的方法, 包可以把模块进行分组分层结构, 可以把包理解成一个包含多个模块的文件夹.
创建一个包同样非常简单, 只需要将几个模块放到一个文件夹目录中, 然后在目录中创建一个__init__.py文件, 即可将这个文件夹转化成一个包.
如上例即创建了一个包含2个模块 module1 和 module2 的包 package.
导入包中的模块, 需要带上包的名称. 或者使用 from ... import ... 语法
In [1]: import package
In [2]: import package.mudule1
In [3]: from package import mudule2
包中可以包含任意深度嵌套子包, 大型项目中, 可以创建复杂的目录结构来组织代码.
2 导入语句 import
2.1 部分导入
有些时候我们只需要模块中的某一个对象, 可以选择不导入整个模块的方式, 使用部分导入 from <module> import <name>
In [1]: from math import pi
In [2]: pi
Out[2]: 3.141592653589793
2.2 ☹️全部导入
使用 from <module> import * 可以一次将模块下所有对象导入到当前空间.使用这个方法将我们之前创建的module.py模块导入.
In [1]: from module import *
In [2]: var
Out[2]: 'right.'
In [3]: array
Out[3]: [10, 20, 30]
In [4]: func('data')
arg = data
In [5]: demo = Demo()
In [6]: demo
Out[6]: <module.Demo at 0x24aaa748248>
使用module中的对象, 可以看到不再需要用module.var, 而是直接使用var.
实际使用中尽量杜绝这种使用方式, 因为这样做会污染当前的命名空间, 导致无意中覆盖现有名称, 而且会降低代码的可读性, 当使用了多条from ... import * 语句之后, 无法判断使用的对象来自于哪个模块.
2.3 导入别名 import as
有些时候导入的模块在本模块已经存在同名文件, 为了避免被覆盖, 可以使用别名将导入重新命名.
In [1]: import json
In [2]: def json(s):
...: print(s)
In [3]: json.dumps([1,2])
---------------------------------------------------------------------------
AttributeError: 'function' object has no attribute 'dumps'
如上例代码, json被本地函数覆盖, 调用json模块的dumps函数时报错.
将模块名重命名为js, 避免了名称与本地名称冲突
In [4]: import json as js
In [5]: js.dumps([1,2])
Out[5]: '[1, 2]'
另一个场景是, 导入的模块名称较长, 为了后续使用方便将其简写
In [1]: import matplotlib as plt
In [2]: from pandas import dataframe as df
2.4 导入模块搜索路径
当解释器执行 import 语句时, 会尝试从一个路径的列表进行搜索. 列表中的路径有3个来源
- 当前运行python脚本的目录
- PYTHONPATH环境变量中包含的目录列表
- 在安装 Python 时配置的依赖于安装的目录列表
生成的搜索路径可在 Python 变量中访问, 可通过sys.path来查看
>>> import sys
>>> sys.path
['', 'C:\\Python39\\python39.zip', 'C:\\Python39\\DLLs', 'C:\\Python39\\lib',
'C:\\Python39',
'C:\\Python39\\lib\\site-packages',
'C:\\Python39\\lib\\site-packages\\win32',
'C:\\Python39\\lib\\site-packages\\win32\\lib',
'C:\\Python39\\lib\\site-packages\\Pythonwin']
2.5 相对导入
使用之前的例子, 一个如下图结构的package包. 在 module 中需要使用subpackage.module1中的对象
尝试在 module 中 导入module2
import sys
for p in sys.path:
print(p)
from package import module2
> python module.py
C:\Users\Desktop\package\subpackage
C:\Python39\python39.zip
C:\Python39\DLLs
C:\Python39\lib
...
ModuleNotFoundError: No module named 'package'
从打印的 sys.path 可以看到, 模块搜索路径在子目录subpackage下, 所以找不到package模块.
2个解决方法,
2.5.1 手动添加目录到 sys.path
将之前的代码, 增加一行 sys.path.insert(0, "C:\\Users\\Desktop")
, 手动将 package 所在目录 添加到 sys.path.
import sys
sys.path.insert(0, "C:\\Users\\Desktop")
for p in sys.path:
print(p)
from package import module2
再次执行, 发现可以import 成功
> python module.py
C:\Users\Desktop
C:\Users\Desktop\package\subpackage
C:\Python39\python39.zip
C:\Python39\DLLs
C:\Python39\lib
...
right
2.5.2 ☹️使用相对导入
todo
不建议使用相对导入
总结
本文解释了模块和包的概念,
导入模块和包时, import 的常用使用方法