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 导入模块时,会按照以下顺序搜索:

  1. 当前目录 - 脚本所在目录
  2. PYTHONPATH - 环境变量中的目录
  3. 标准库目录 - Python 安装目录
  4. 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() 动态导入

考前记忆

面试重点

  1. Python 模块搜索顺序(高频)

    • 当前目录 → PYTHONPATH → 标准库 → site-packages
  2. __name__ 的两个值

    • __main__:表示文件被直接运行
    • 模块名:表示文件被作为模块导入
  3. 导入方式对比

    import A          # 需用 A.xxx 访问
    from A import x   # 直接使用 x
    from A import *   # 导入所有公开成员(不推荐)
    
  4. sys.path 是列表,可以动态修改

  5. 相对导入只用于包内,且 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(当直接运行此脚本时,namemain


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 依次:

  1. sys.modules 中查找是否已加载该模块
  2. 若未加载,按 sys.path 顺序搜索模块文件
  3. 找到后编译为字节码(.pyc 文件)
  4. 执行模块代码,创建模块对象
  5. 将模块对象存入 sys.modules
  6. 在当前命名空间创建对模块的引用

Q2: 什么是 __all__ 变量,它有什么作用?

__all__ 是一个字符串列表,用于控制 from module import * 的行为。

  • 若定义了 __all__,则只导入列表中的成员
  • 若未定义,则导入所有不以 _ 开头的成员
  • 这是 Python 的显式优于隐式原则的体现

Q3: 何时使用相对导入?何时使用绝对导入?

类型 语法 适用场景
绝对导入 from package import module Python 3+ 推荐,清晰明确
相对导入 from . import module 仅用于包内部,避免与标准库冲突

Python 3 建议:优先使用绝对导入,相对导入仅在包内部使用。


参考资料