Day32 - 模块与导入系统详解
详细讲解
1. 模块基础概念
模块(Module) 是 Python 中组织代码的基本单位。一个 .py 文件就是一个模块,模块中可以包含函数、类、变量以及可执行代码。
# mymodule.py
def greet(name):
return f"你好,{name}!"
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
PI = 3.14159
2. import 语句详解
2.1 基本导入
import mymodule
# 调用函数
result = mymodule.greet("张三")
print(result) # 输出: 你好,张三!
# 使用类
calc = mymodule.Calculator()
print(calc.add(10, 5)) # 输出: 15
# 访问变量
print(mymodule.PI) # 输出: 3.14159
2.2 from…import 语句
# 导入特定成员
from mymodule import greet, PI
# 直接使用(无需模块名前缀)
print(greet("李四")) # 输出: 你好,李四!
print(PI) # 输出: 3.14159
# 导入所有成员(不推荐)
from mymodule import *
2.3 as 别名使用
import mymodule as m
# 使用简短别名
print(m.greet("王五")) # 输出: 你好,王五!
# 部分导入起别名
from mymodule import Calculator as Calc, PI as PI_VALUE
calc = Calc()
print(calc.add(100, 200)) # 输出: 300
3. sys.path 与模块搜索路径
Python 导入模块时,会按照以下顺序搜索:
- 当前目录 - 脚本所在目录
- PYTHONPATH - 环境变量中的目录
- 标准库目录 - Python 安装目录
- site-packages - 第三方包目录
import sys
# 查看所有搜索路径
print("模块搜索路径:")
for i, path in enumerate(sys.path):
print(f" {i}: {path}")
# 添加自定义搜索路径
sys.path.append('/path/to/my/modules')
# 插入到最前面(优先搜索)
sys.path.insert(0, '/path/to/my/modules')
# 验证路径已添加
print("\n添加后的路径:")
print(sys.path)
4. 包(Package)结构
mypackage/ __init__.py # 包初始化文件 module1.py # 模块1 module2.py # 模块2 subpackage/ __init__.py module3.py# 导入包中的模块
import mypackage.module1
from mypackage import module2
from mypackage.subpackage import module3
5. all 变量控制导入行为
# mymodule.py
__all__ = ['public_function', 'PublicClass']
def public_function():
return "这是公开函数"
def private_function():
return "这是私有函数"
class PublicClass:
pass
class _PrivateClass:
pass
# from mymodule import * 只会导入 public_function 和 PublicClass
6. 模块的特殊属性
import mymodule
# __name__: 模块名(主模块为 '__main__')
print(mymodule.__name__) # 输出: mymodule
# __file__: 模块文件路径
print(mymodule.__file__)
# __doc__: 模块文档字符串
print(mymodule.__doc__)
# __package__: 包名
print(mymodule.__package__)
# __spec__: 模块规范信息
print(mymodule.__spec__)
7. 主模块与 __main__
# example.py
def main():
print("主函数执行")
if __name__ == '__main__':
# 只有直接运行此文件时才执行
main()
print("作为主程序运行")
else:
print(f"作为模块被导入,模块名: {__name__}")
8. 动态导入与 importlib
import importlib
# 动态导入模块
math_module = importlib.import_module('math')
print(math_module.sqrt(16)) # 输出: 4.0
# 动态导入指定模块的成员
import importlib.util
spec = importlib.util.spec_from_file_location("mymodule", "/path/to/mymodule.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# 使用成员的别名
public_function = getattr(module, 'public_function')
print(public_function())
9. 相对导入与绝对导入
# Python 3+ 推荐使用绝对导入
# mypackage/subpackage/module.py
from mypackage import module1 # 绝对导入
from . import module2 # 相对导入(同级)
from .. import module3 # 相对导入(上级)
from ..parent_module import something # 上级的具体模块
10. 重新加载模块
import importlib
import mymodule
# 重新加载模块(用于开发时)
importlib.reload(mymodule)
背诵版
核心概念速记
┌─────────────────────────────────────────────────────────────┐ │ Python 导入系统 │ ├─────────────────────────────────────────────────────────────┤ │ import mymodule # 导入整个模块 │ │ from mymodule import fn # 导入特定成员 │ │ import mymodule as m # 使用别名 │ │ from m import fn as f # 组合使用 │ ├─────────────────────────────────────────────────────────────┤ │ sys.path 搜索顺序: │ │ 1. 当前目录 → 2. PYTHONPATH → 3. 标准库 → 4. site-packages │ ├─────────────────────────────────────────────────────────────┤ │ __name__ == '__main__' # 判断是否为主程序 │ │ __all__ # 控制 from * 导入行为 │ │ importlib.reload() # 重新加载模块 │ └─────────────────────────────────────────────────────────────┘命令速查表
| 语法 | 说明 |
|---|---|
import os |
导入标准库模块 |
from os import path |
导入特定成员 |
import os as operating_system |
使用别名 |
from . import module |
相对导入(同级) |
from .. import module |
相对导入(上级) |
sys.path.append() |
添加搜索路径 |
importlib.import_module() |
动态导入 |
考前记忆
面试重点
-
Python 模块搜索顺序(高频)
- 当前目录 → PYTHONPATH → 标准库 → site-packages
-
__name__的两个值__main__:表示文件被直接运行- 模块名:表示文件被作为模块导入
-
导入方式对比
import A # 需用 A.xxx 访问 from A import x # 直接使用 x from A import * # 导入所有公开成员(不推荐) -
sys.path是列表,可以动态修改 -
相对导入只用于包内,且 Python 3 需要
from __future__ import annotations
记忆口诀
导入模块要记清, 顺序路径在sys中。 __name__判主从, __all__控导入。 别名as来帮忙, importlib动加载。测试题
选择题
1. 下面哪个选项可以正确导入 math 模块中的 sqrt 函数?
# A.
import math
math.sqrt(16)
# B.
from math import sqrt
sqrt(16)
# C.
import math as m
m.sqrt(16)
# D.
以上全部正确
答案:D
2. 当 Python 执行 import sys 时,模块搜索路径的正确顺序是?
# A. site-packages → 标准库 → PYTHONPATH → 当前目录
# B. 当前目录 → PYTHONPATH → 标准库 → site-packages
# C. 标准库 → site-packages → 当前目录 → PYTHONPATH
# D. PYTHONPATH → 当前目录 → 标准库 → site-packages
答案:B
3. 以下代码的输出是什么?
# module_test.py
print(f"模块名: {__name__}")
if __name__ == '__main__':
print("直接运行")
else:
print("被导入")
A. 直接运行 B. 被导入 C. 模块名: main / 直接运行 D. 模块名: main / 被导入
答案:C(当直接运行此脚本时,name 为 main)
4. from mypackage import * 受哪个变量的控制?
# A. __init__
# B. __all__
# C. __name__
# D. __package__
答案:B
5. 如何动态导入一个模块?
# A.
import mymodule
# B.
import 'mymodule'
# C.
importlib.import_module('mymodule')
# D.
load('mymodule')
答案:C
编程题
1. 编写一个模块 string_utils.py,包含以下功能:
# string_utils.py
"""字符串处理工具模块"""
__all__ = ['reverse_string', 'count_vowels', 'is_palindrome']
def reverse_string(s):
"""反转字符串"""
return s[::-1]
def count_vowels(s):
"""统计元音字母数量"""
vowels = 'aeiouAEIOU'
return sum(1 for c in s if c in vowels)
def is_palindrome(s):
"""判断是否为回文"""
return s == s[::-1]
def _private_helper(s):
"""私有辅助函数(不会被 * 导入)"""
return len(s)
2. 创建一个包结构,并使用各种导入方式:
# 结构:
# myapp/
# __init__.py
# utils/
# __init__.py
# text.py
# math_tools.py
# myapp/__init__.py
"""我的应用包"""
__version__ = "1.0.0"
# myapp/utils/__init__.py
from .text import clean_text, count_words
from .math_tools import factorial
__all__ = ['clean_text', 'count_words', 'factorial']
# myapp/utils/text.py
def clean_text(text):
"""清理文本"""
return ' '.join(text.split())
def count_words(text):
"""统计词数"""
return len(clean_text(text).split())
# myapp/utils/math_tools.py
def factorial(n):
"""计算阶乘"""
if n < 0:
raise ValueError("负数没有阶乘")
if n <= 1:
return 1
return n * factorial(n - 1)
3. 使用 importlib 动态加载模块:
import importlib.util
import sys
def load_module_from_path(module_name, file_path):
"""从指定路径动态加载模块"""
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
# 使用示例
# module = load_module_from_path('mymod', '/path/to/mymodule.py')
# module.greet("世界")
问答题
Q1: 请解释 Python 中 import 语句的执行过程。
执行 import mymodule 时,Python 依次:
- 在
sys.modules中查找是否已加载该模块 - 若未加载,按
sys.path顺序搜索模块文件 - 找到后编译为字节码(
.pyc文件) - 执行模块代码,创建模块对象
- 将模块对象存入
sys.modules - 在当前命名空间创建对模块的引用
Q2: 什么是 __all__ 变量,它有什么作用?
__all__ 是一个字符串列表,用于控制 from module import * 的行为。
- 若定义了
__all__,则只导入列表中的成员 - 若未定义,则导入所有不以
_开头的成员 - 这是 Python 的显式优于隐式原则的体现
Q3: 何时使用相对导入?何时使用绝对导入?
| 类型 | 语法 | 适用场景 |
|---|---|---|
| 绝对导入 | from package import module |
Python 3+ 推荐,清晰明确 |
| 相对导入 | from . import module |
仅用于包内部,避免与标准库冲突 |
Python 3 建议:优先使用绝对导入,相对导入仅在包内部使用。