模块(module)和包(package)
模块
模块和函数充当着同样的角色,都会为了更好的组织代码,提高代码的复用性。当一个文件被当作模块导入时,其内部的代码会被执行。
模块实际就是一个包含 Python 代码的文件,文件名就是模块名。可以通过 __name__
属性获取模块名。
def fib(n):
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
import fibo
print(fibo.__name__) # fibo
每个模块都有自己的命名空间,通过模块名来访问其属性。不用担心全局变量污染的问题。
可以通过 from ... import ...
语句导入模块中的指定属性。将模块中的属性导入到当前命名空间中。多个属性之间用逗号分隔。
from fibo import fib, fib2
fib(1000)
也可以通过 from ... import *
语句导入模块中的所有属性。 会导入所有不以下划线(_
)开头的名称,不要轻易使用这种方式,可能会导致命名空间污染。
from fibo import *
fib(1000)
可以通过 as
关键字对导入的属性或者模块进行重命名。
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
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__
的值则是模块的名称。
这个特性常被用来区分代码是被直接执行还是被导入:
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)}")
# 导入 calculator 模块
import calculator
# 这里只会导入函数定义,不会执行测试代码
result = calculator.add(10, 20)
print(f"10 + 20 = {result}") # 输出: 10 + 20 = 30
当我们使用 if __name__ == "__main__":
语句时:
直接运行
calculator.py
时:__name__
的值为"__main__"
- if 条件为真,测试代码会执行
- 输出测试结果
作为模块导入
calculator.py
时:__name__
的值为"calculator"
(模块名)- if 条件为假,测试代码被跳过
- 只有函数定义被导入
这种模式的主要优势:
- 可以在模块中包含测试代码
- 避免导入模块时执行不必要的代码
- 使模块既可以作为库使用,又可以独立运行测试
- 保持代码的整洁性和模块化
模块的搜索路径
当导入一个模块时,Python 会按照以下顺序搜索模块:
- 当前目录
- 环境变量
PYTHONPATH
中的目录 - 标准库目录
搜索路径存储在 sys
模块的 path
变量中。
import sys
print(sys.path)
当导入的模块不存在时,Python 会报错,可以通过 sys.path.append()
方法临时将路径添加到搜索路径中。
import sys
sys.path.append('/Users/yun/Desktop/python-note/data/note/python/04')
import nonexistent_module
dir 函数
dir 函数可以列出模块中的所有属性。返回结果经过排序的字符串列表。
import fibo
print(dir(fibo))
包
包是包含多个模块的目录。包的目录下必须有一个 __init__.py
文件,用于标识该目录是一个包。该文件可以为空,也可以包含初始化代码。
|-- package
| |-- __init__.py
| |-- users.py
| |-- detail.py
|-- main.py
常规的导入与模块的导入方式相同,只是路径需要使用包的路径。
from package import users, detail
users.get_list()
detail.get_detail()
从包中导入 *
包中的 *
导入的是 __init__.py
文件中的声明的 __all__
属性。
__all__ = ['users', 'detail']
from package import *
users.get_list()
detail.get_detail()
如果 __all__
中包含了与模块名相同的属性,模块将会被属性替换。
__all__ = ['users', 'detail']
def users():
print('users')
def detail():
print('detail')
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
def main():
print('this is sub_package')
from . import utils
from ..sub_package import main as sub_main
def main():
utils.get_tools()
sub_main.main()
print('this is child_package')
from package.sub_package import main as sub_main
from package.child_package import main as child_main
sub_main()
child_main()
dir 函数
dir 函数可以列出模块中的所有属性。返回结果经过排序的字符串列表。
import fibo
print(dir(fibo)) # ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fib', 'fib2']
练习题
基础练习
模块导入
python# 创建一个名为 calculator.py 的模块,包含以下函数: # - add(a, b): 返回两数之和 # - subtract(a, b): 返回两数之差 # - multiply(a, b): 返回两数之积 # - divide(a, b): 返回两数之商 # 然后在另一个文件中导入并使用这些函数
模块重命名
python# 基于上面的 calculator.py # 使用三种不同的方式导入 add 函数: # 1. 直接导入模块 # 2. 从模块中导入特定函数 # 3. 导入时使用别名 # 并分别调用实现 1 + 2 的运算
包的创建与使用
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): 计算阶乘
进阶练习
模块搜索路径
python# 编写一个程序,实现以下功能: # 1. 打印当前的模块搜索路径 # 2. 添加一个新的搜索路径 # 3. 验证新路径是否已被添加 # 4. 从新添加的路径中导入一个模块
包的相对导入
python# 创建以下包结构: # myapp/ # ├── __init__.py # ├── main.py # └── utils/ # ├── __init__.py # ├── formatter.py # └── validator.py # # 要求: # 1. 在 formatter.py 中定义格式化函数 # 2. 在 validator.py 中定义验证函数 # 3. 在 main.py 中使用相对导入导入这些函数
挑战题
- 模块化计算器应用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 中提供交互式界面
参考答案
点击查看答案
基础练习答案
模块导入
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
模块重命名
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)
包的创建与使用
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)
进阶练习答案
模块搜索路径
pythonimport 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("模块导入失败")
包的相对导入
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 }
挑战题答案
模块化计算器应用
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
建议先独立完成练习,遇到困难时再参考答案。这样能够更好地掌握所学知识。