模块Module和包Package
在Python中,一个 .py文件 就称之为一个模块(Module)。Module的目的是代码的划分、管理以及代码重用,在一个Module中可以存在多个类、函数甚至是需要预执行的脚本。 模块一共三种:python标准库、第三方模块、应用程序自定义模块。 相同名字的函数和变量完全可以分别存在不同的模块中,不必考虑名字会与其他模块冲突。但是不要与内置函数名字冲突。
相关概念: -
_pycache__
:Python3.2版本引入的,用于存放模块的编译后的字节码文件,以提高模块的加载速度。
-
__init__.py
:模块的初始化文件,可以为空,也可以有代码。**当一个文件夹下有__init__.py文件时,这个文件夹就会被当作一个包来处理。**
- dir()
:查看模块的所有属性和方法。比如:
import math
print(dir(math))
-
__name__
:模块的内置属性,用于判断模块是被导入还是直接当作脚本执行。比如:
if __name__ == '__main__':
print('This is a module')
模块的查找路径
在 Python 中,当运行一个项目时,模块的查找路径遵循特定的顺序。这是由
sys.path
列表定义的,该列表按顺序列出了 Python
在查找模块时会搜索的路径。
模块查找路径的顺序 - 当前目录:运行脚本所在的目录。如果你执行了一个脚本,那么 Python 会首先尝试从该脚本所在的目录加载模块。 - 环境变量 PYTHONPATH 指定的目录:如果设置了 PYTHONPATH 环境变量,其值中列出的目录将被加入查找路径。 - 标准库目录:Python 自带的标准库路径(例如 lib 或 Lib 目录)。 - 第三方包路径:通常是安装在 site-packages 目录下的路径。 - 内置模块:如果以上路径都找不到,Python 会尝试加载内置模块(如 sys、os)。
示例及详细解释: 比如一个项目的目录结构如下:
project/
│
├── script.py # 主运行脚本
├── module1.py # 自定义模块
└── subdir/
├── module2.py # 自定义模块
└── __init__.py # 将 subdir 标记为包
其中 script.py
代码如下:
import sys
print("Module search paths:")
for path in sys.path:
print(path)
import module1 # 自定义模块,位于当前目录
from subdir import module2 # 从子目录中导入模块
模块查找过程如下: 1. 当前目录: - 运行 python script.py 时,Python 首先查看当前目录(project/)。 - 找到 module1.py,并成功导入。 2. 子目录导入: - 对于 from subdir import module2,Python 进入 project/subdir/ 并找到 module2.py。 - 因为 subdir 包含 init.py,它被认为是一个包。 3. 标准库: - 如果你尝试导入如 os、sys 等模块,Python 会从标准库路径中加载。 4. 第三方包: - 如果尝试导入第三方库(如 numpy),Python 会在 site-packages 目录中查找。
修改模块查找路径
可以动态修改 sys.path 来影响模块的查找路径。
import sys
import os
# 查看当前路径
print("Before modification:", sys.path)
# 添加新路径
new_path = os.path.join(os.getcwd(), 'subdir')
sys.path.insert(0, new_path)
print("After modification:", sys.path)
# 导入模块
import module2
与包package的关系
包所在的目录需要包含一个空文件__init__.py来表明这个是一个包,所以说包是一个包含了多个模块的目录。包的目的是为了组织模块,以便更好地管理和维护代码。
在__init__.py
文件中可以声明一些描述性的代码来变更package的特性。比如针对import
*的__all__,如果某个package下有你明确不希望被引用的py文件,可以通过__all__明确说明哪些是希望引入的,这样在python处理import
*时会忽略掉不在__all__列表的内容。比如:
__all__ = ['module1', 'module2'] # 只允许导入module1和module2
### 模块的导入
#### 同级目录下的模块导入
如果你要使用的模块(py文件)和当前模块在同一目录,只要import相应的文件名就好,比如在当前目录下有一个module1.py文件,那么在另一个文件中可以直接使用import
module1来导入。 -
一个模块可以在当前位置import多次,但是只有第一次导入会执行内容,其他的都为引用内存
- 更改调用名称:import module1 as m1
-
只导入模块中的部分内容:from module1 import func1
不同目录下的模块导入
子目录下的模块导入
假设目录结构如下:
project/
├── main.py
├── subdir/
│ ├── module2.py
│ └── __init__.py
方法
1:直接导入(如果子目录是一个包) 如果子目录包含
__init__.py
文件(即被视为包),可以使用以下方式导入:
# main.py
from subdir import module2 # 导入子目录中的模块
module2.some_function() # 调用 module2 中的函数
方法 2:修改 sys.path 如果子目录中没有
__init__.py
,或者希望在没有包的情况下导入,可以通过添加子目录到
sys.path:
# main.py
import sys
import os
# 将子目录路径添加到 sys.path
sys.path.append(os.path.join(os.getcwd(), 'subdir'))
import module2 # 现在可以直接导入
module2.some_function()
父目录下的模块导入
假设目录结构如下:
project/
├── parent_module.py
└── subdir/
├── main.py
├── module3.py
└── __init__.py
方法 1:使用 sys.path
# main.py
import sys
import os
# 将父目录路径添加到 sys.path
sys.path.append(os.path.dirname(os.getcwd()))
import parent_module # 现在可以导入父目录的模块
parent_module.some_function()
方法 2:相对导入(仅适用于包结构)
如果 subdir 是一个包(包含 init.py),可以使用相对导入:
# main.py
from .. import parent_module # 相对导入父目录的模块
parent_module.some_function()
注意:相对导入仅在包结构中有效,且不能直接运行该脚本(需要通过
python -m 的方式运行)。
其他
- 当使用
from module import func_b
时,整个模块会被加载和执行一次(包括下划线开头的私有对象),但只有你导入的部分会被绑定到当前的命名空间 例如:# module.py value = 1 def func_a(): print("Function func_a is being executed") return 0 def func_b(): print("Function func_b is being executed") b = func_a() return b
# main.py
from module import func_b
print("Calling func_b() in main.py")
result = func_b()
print(f"Result: {result}")
执行过程: 1.
from module import func_b
Python 会先找到 module.py 文件并加载整个模块: - Python 会解释并执行 module.py 文件的顶层代码。 - value = 1 会被执行并存储到 module 的命名空间中。 - 函数 func_a 和 func_b 的定义会被加载,并绑定到 module 的命名空间。 - 然后,func_b 会被绑定到当前模块(main.py)的命名空间。
注意: - 虽然整个模块被加载,但只有 func_b 被绑定到 main.py 的命名空间中。 - 在加载过程中,module 的顶层代码(如赋值语句、类定义等)都会被执行一次。
- 调用 func_b()
- Python 跳转到 module.py 中 func_b 的定义,并开始执行该函数的内容。
- 在 func_b 中,func_a 被调用,导致 func_a 的代码被执行。 完整输出:
Calling func_b() in main.py Function func_b is being executed Function func_a is being executed Result: 0
- init.py文件的的规范
加入存在以下目录结构:
project/ ├── my_package/ │ ├── module1.py │ ├── module2.py │ └── __init__.py └── main.py
__init__.py
文件的一些写法的解释:
- 空:仅标记为包,导入需要显式地指明模块和内容。所以使用时也是
import my_package.module1
,本质上是模块,包中的定义的__all__列表不会生效。 from . import module2
: 导入完整模块到包命名空间,使用时需通过模块名访问,本质上是模块,包中的定义的__all__列表不会生效。from my_package import *
: 导入完整模块到包命名空间,使用时无需模块名访问,from .module2 import *
: 将整个模块内容导入到包命名空间,使用时无需模块名。from my_package.module2 import *
: 将整个模块内容导入到包命名空间,使用时无需模块名。from module2 import *
: 这是种错误写法,无法找到模块。
__all__
在__init__.py
中的作用:
- 当你使用 from my_package import * 时,
__all__
定义了哪些名称会被导入到当前命名空间。 - 如果__all__没有定义,那么默认会导入所有非以下划线 _ 开头的对象。
调用时的区别: - 如果在 main.py
中需要写模块名(例如
my_package.module2.func2()),那么
__all__
不会限制模块中的内容。 - __all__
只在
from my_package import * 时生效,而不会影响显式导入 例如: 目录结构:
my_package/
├── __init__.py
├── module1.py
├── module2.py # 包含 func2 函数
#__init__.py:
from .module2 import *
__all__ = [] # 不对外暴露任何内容
# main.py
from my_package import *
func2() # 报错,因为 __all__=[] 限制了导出的内容
from my_package.module2 import func2
func2() # 正常运行
from my_package import func2
func2() # 正常运行, 比较奇怪哈,但是测试了这样子是可以的
- 通过
-m
运行一个模块 对于一些项目,我们可能需要通过命令行来运行一个模块,这时候可以使用-m
参数,以支持模块的相对导入。
对于moulde3.py中的内容,如果写成:project/ ├── my_package/ │ ├── module1.py │ ├── module2.py │ └── __init__.py └── subdir/ ├── module3.py └── __init__.py
需要通过from my_package import func1 # module1.py中的函数 func1()
-m
参数来运行:python -m subdir.module3
,这样才能正确导入my_package中的内容。
参考: > https://blog.csdn.net/Uncle_GUO/article/details/80867086