Skip to content

模块(module)和包(package)

模块

模块和函数充当着同样的角色,都会为了更好的组织代码,提高代码的复用性。当一个文件被当作模块导入时,其内部的代码会被执行。

模块实际就是一个包含 Python 代码的文件,文件名就是模块名。可以通过 __name__ 属性获取模块名。

python
def fib(n):
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
python
import fibo

print(fibo.__name__) # fibo

每个模块都有自己的命名空间,通过模块名来访问其属性。不用担心全局变量污染的问题。

可以通过 from ... import ... 语句导入模块中的指定属性。将模块中的属性导入到当前命名空间中。多个属性之间用逗号分隔。

python
from fibo import fib, fib2

fib(1000)

也可以通过 from ... import * 语句导入模块中的所有属性。 会导入所有不以下划线(_)开头的名称,不要轻易使用这种方式,可能会导致命名空间污染。

python
from fibo import *

fib(1000)

可以通过 as 关键字对导入的属性或者模块进行重命名。

python
import fibo as fib
from fibo import fib as fibonacci

fib.fib(1000)
fibonacci(1000)

深层模块的导入

from 所有的路径 import 模块名 或者 import 至模块名 此种方式需要别名给模块重新命名,如果不指定别名,就需要使用全名。

|-- a
|   |-- b
|   |   |-- c
|   |   |   |-- code.py
|-- main.py
python
from a.b.c import code
import a.b.c.code as code
import a.b.c.code

a.b.c.code.fib(1000)
code.fib(1000)

执行模块

在 Python 中,__name__ 变量是一个特殊的内置变量。当我们直接运行一个 Python 文件时,__name__ 的值会被设置为 "__main__";而当这个文件被当作模块导入时,__name__ 的值则是模块的名称。

这个特性常被用来区分代码是被直接执行还是被导入:

python
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

# 只有直接运行该文件时才会执行的测试代码
if __name__ == "__main__":
    # 这些代码在导入时不会执行
    print("正在测试计算器功能...")
    print(f"1 + 1 = {add(1, 1)}")
    print(f"5 - 3 = {subtract(5, 3)}")
python
# 导入 calculator 模块
import calculator

# 这里只会导入函数定义,不会执行测试代码
result = calculator.add(10, 20)
print(f"10 + 20 = {result}")  # 输出: 10 + 20 = 30

当我们使用 if __name__ == "__main__": 语句时:

  1. 直接运行 calculator.py 时:

    • __name__ 的值为 "__main__"
    • if 条件为真,测试代码会执行
    • 输出测试结果
  2. 作为模块导入 calculator.py 时:

    • __name__ 的值为 "calculator"(模块名)
    • if 条件为假,测试代码被跳过
    • 只有函数定义被导入

这种模式的主要优势:

  • 可以在模块中包含测试代码
  • 避免导入模块时执行不必要的代码
  • 使模块既可以作为库使用,又可以独立运行测试
  • 保持代码的整洁性和模块化

模块的搜索路径

当导入一个模块时,Python 会按照以下顺序搜索模块:

  1. 当前目录
  2. 环境变量 PYTHONPATH 中的目录
  3. 标准库目录

搜索路径存储在 sys 模块的 path 变量中。

python
import sys
print(sys.path)

当导入的模块不存在时,Python 会报错,可以通过 sys.path.append() 方法临时将路径添加到搜索路径中。

python
import sys
sys.path.append('/Users/yun/Desktop/python-note/data/note/python/04')
import nonexistent_module

dir 函数

dir 函数可以列出模块中的所有属性。返回结果经过排序的字符串列表。

python
import fibo
print(dir(fibo))

包是包含多个模块的目录。包的目录下必须有一个 __init__.py 文件,用于标识该目录是一个包。该文件可以为空,也可以包含初始化代码。

|-- package
|   |-- __init__.py
|   |-- users.py
|   |-- detail.py
|-- main.py

常规的导入与模块的导入方式相同,只是路径需要使用包的路径。

python
from package import users, detail

users.get_list()
detail.get_detail()

从包中导入 *

包中的 * 导入的是 __init__.py 文件中的声明的 __all__ 属性。

python
__all__ = ['users', 'detail']
python
from package import *

users.get_list()
detail.get_detail()

如果 __all__ 中包含了与模块名相同的属性,模块将会被属性替换。

python
__all__ = ['users', 'detail']

def users():
    print('users')

def detail():
    print('detail')
python
from package import *

users() # users 上不存在 get_list 方法
detail() # detail 上不存在 get_detail 方法

如果没有 __all__ 属性,* 则导入 __init__.py 中的所有属性。

包的相对导入

在包中,可以使用相对导入来导入包中的其他模块。

|-- package
|   |-- __init__.py
|   |-- sub_package
|   |   |-- __init__.py
|   |   |-- main.py
|   |-- child_package
|   |   |-- __init__.py
|   |   |-- utils.py
|   |   |-- main.py
|-- main.py
python
def main():
    print('this is sub_package')
python
from . import utils
from ..sub_package import main as sub_main

def main():
    utils.get_tools()
    sub_main.main()
    print('this is child_package')
python
from package.sub_package import main as sub_main
from package.child_package import main as child_main

sub_main()
child_main()

dir 函数

dir 函数可以列出模块中的所有属性。返回结果经过排序的字符串列表。

python
import fibo
print(dir(fibo)) # ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fib', 'fib2']

练习题

基础练习

  1. 模块导入

    python
    # 创建一个名为 calculator.py 的模块,包含以下函数:
    # - add(a, b): 返回两数之和
    # - subtract(a, b): 返回两数之差
    # - multiply(a, b): 返回两数之积
    # - divide(a, b): 返回两数之商
    # 然后在另一个文件中导入并使用这些函数
  2. 模块重命名

    python
    # 基于上面的 calculator.py
    # 使用三种不同的方式导入 add 函数:
    # 1. 直接导入模块
    # 2. 从模块中导入特定函数
    # 3. 导入时使用别名
    # 并分别调用实现 1 + 2 的运算
  3. 包的创建与使用

    python
    # 创建一个名为 utils 的包,包含以下结构:
    # utils/
    #   ├── __init__.py
    #   ├── string_utils.py
    #   └── math_utils.py
    # 
    # string_utils.py 包含函数:
    # - reverse(s): 反转字符串
    # - count_chars(s): 统计字符出现次数
    #
    # math_utils.py 包含函数:
    # - is_prime(n): 判断是否为质数
    # - factorial(n): 计算阶乘

进阶练习

  1. 模块搜索路径

    python
    # 编写一个程序,实现以下功能:
    # 1. 打印当前的模块搜索路径
    # 2. 添加一个新的搜索路径
    # 3. 验证新路径是否已被添加
    # 4. 从新添加的路径中导入一个模块
  2. 包的相对导入

    python
    # 创建以下包结构:
    # myapp/
    #   ├── __init__.py
    #   ├── main.py
    #   └── utils/
    #       ├── __init__.py
    #       ├── formatter.py
    #       └── validator.py
    #
    # 要求:
    # 1. 在 formatter.py 中定义格式化函数
    # 2. 在 validator.py 中定义验证函数
    # 3. 在 main.py 中使用相对导入导入这些函数

挑战题

  1. 模块化计算器应用
    python
    # 创建一个完整的计算器应用,包含以下结构:
    # calculator/
    #   ├── __init__.py
    #   ├── operations/
    #   │   ├── __init__.py
    #   │   ├── basic.py  # 基本运算
    #   │   └── advanced.py  # 高级运算(幂运算、对数等)
    #   ├── utils/
    #   │   ├── __init__.py
    #   │   └── validator.py  # 输入验证
    #   └── main.py  # 主程序
    #
    # 要求:
    # 1. 实现基本的四则运算
    # 2. 实现至少两个高级运算
    # 3. 实现输入验证
    # 4. 使用相对导入
    # 5. 在 main.py 中提供交互式界面

参考答案

点击查看答案

基础练习答案

  1. 模块导入

    python
    # calculator.py
    def add(a, b):
        return a + b
    
    def subtract(a, b):
        return a - b
    
    def multiply(a, b):
        return a * b
    
    def divide(a, b):
        if b == 0:
            raise ValueError("除数不能为0")
        return a / b
    
    if __name__ == "__main__":
        print(add(10, 5))  # 测试代码
    python
    # main.py
    import calculator
    
    print(calculator.add(5, 3))      # 8
    print(calculator.multiply(4, 2))  # 8
  2. 模块重命名

    python
    # 方式1:直接导入模块
    import calculator
    result1 = calculator.add(1, 2)
    
    # 方式2:从模块中导入特定函数
    from calculator import add
    result2 = add(1, 2)
    
    # 方式3:导入时使用别名
    import calculator as calc
    result3 = calc.add(1, 2)
  3. 包的创建与使用

    python
    # utils/string_utils.py
    def reverse(s):
        return s[::-1]
    
    def count_chars(s):
        return {char: s.count(char) for char in set(s)}
    python
    # utils/math_utils.py
    def is_prime(n):
        if n < 2:
            return False
        for i in range(2, int(n ** 0.5) + 1):
            if n % i == 0:
                return False
        return True
    
    def factorial(n):
        if n < 0:
            raise ValueError("负数没有阶乘")
        if n == 0:
            return 1
        return n * factorial(n - 1)

进阶练习答案

  1. 模块搜索路径

    python
    import sys
    import os
    
    # 打印当前搜索路径
    print("当前的模块搜索路径:")
    for path in sys.path:
        print(path)
    
    # 添加新路径
    new_path = "/path/to/your/modules"
    sys.path.append(new_path)
    
    # 验证新路径
    print("\n新路径是否已添加:", new_path in sys.path)
    
    # 从新路径导入模块
    try:
        import custom_module
        print("成功导入模块")
    except ImportError:
        print("模块导入失败")
  2. 包的相对导入

    python
    # myapp/utils/formatter.py
    def format_name(name):
        return name.strip().title()
    
    def format_date(date):
        return date.strftime("%Y-%m-%d")
    python
    # myapp/utils/validator.py
    def validate_email(email):
        return '@' in email and '.' in email
    
    def validate_phone(phone):
        return len(phone) == 11 and phone.isdigit()
    python
    # myapp/main.py
    from .utils.formatter import format_name, format_date
    from .utils.validator import validate_email, validate_phone
    
    def process_user_data(name, email, phone):
        if not validate_email(email):
            raise ValueError("Invalid email")
        if not validate_phone(phone):
            raise ValueError("Invalid phone")
            
        return {
            "name": format_name(name),
            "email": email,
            "phone": phone
        }

挑战题答案

  1. 模块化计算器应用

    python
    # calculator/operations/basic.py
    def add(a, b):
        return a + b
    
    def subtract(a, b):
        return a - b
    
    def multiply(a, b):
        return a * b
    
    def divide(a, b):
        if b == 0:
            raise ValueError("除数不能为0")
        return a / b
    python
    # calculator/operations/advanced.py
    import math
    
    def power(a, b):
        return a ** b
    
    def sqrt(a):
        if a < 0:
            raise ValueError("不能对负数开平方")
        return math.sqrt(a)
    
    def log(a, base=math.e):
        if a <= 0:
            raise ValueError("对数的真数必须为正数")
        return math.log(a, base)
    python
    # calculator/utils/validator.py
    def validate_number(num_str):
        try:
            float(num_str)
            return True
        except ValueError:
            return False
    
    def validate_operation(op, valid_ops):
        return op in valid_ops
    python
    # calculator/main.py
    from .operations import basic, advanced
    from .utils import validator
    
    def main():
        operations = {
            '+': basic.add,
            '-': basic.subtract,
            '*': basic.multiply,
            '/': basic.divide,
            '^': advanced.power,
            'sqrt': advanced.sqrt,
            'log': advanced.log
        }
        
        while True:
            print("\n可用操作:", ', '.join(operations.keys()))
            op = input("请输入操作(q退出): ")
            
            if op == 'q':
                break
                
            if not validator.validate_operation(op, operations.keys()):
                print("无效的操作")
                continue
                
            try:
                if op == 'sqrt':
                    num = input("请输入数字: ")
                    if not validator.validate_number(num):
                        print("无效的输入")
                        continue
                    result = operations[op](float(num))
                else:
                    num1 = input("请输入第一个数字: ")
                    num2 = input("请输入第二个数字: ")
                    if not all(validator.validate_number(x) for x in [num1, num2]):
                        print("无效的输入")
                        continue
                    result = operations[op](float(num1), float(num2))
                
                print(f"结果: {result}")
            except ValueError as e:
                print(f"错误: {e}")
            except Exception as e:
                print(f"发生错误: {e}")
    
    if __name__ == "__main__":
        main()

TIP

建议先独立完成练习,遇到困难时再参考答案。这样能够更好地掌握所学知识。