python-模块/包的导入


模块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 的方式运行)。

其他

  1. 当使用 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 的顶层代码(如赋值语句、类定义等)都会被执行一次。

  1. 调用 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
  1. 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 *: 这是种错误写法,无法找到模块。
  1. __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()  # 正常运行, 比较奇怪哈,但是测试了这样子是可以的

  1. 通过-m运行一个模块 对于一些项目,我们可能需要通过命令行来运行一个模块,这时候可以使用-m参数,以支持模块的相对导入。
    project/
    ├── my_package/
    │   ├── module1.py
    │   ├── module2.py
    │   └── __init__.py
    └── subdir/
        ├── module3.py
        └── __init__.py
    对于moulde3.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


文章作者: 庞贝堡垒
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 庞贝堡垒 !
评论
  目录