Python & AI Tutorials Logo
Python 编程

22. 使用模块与包组织代码

随着你的 Python 程序变得越来越大,把所有代码都放在一个文件里就会变得不切实际。你会想把相关的函数(function)、类(class)和变量(variable)组织到不同的文件中,以便在不同程序之间复用。Python 的模块(module)包(package)系统正好提供了这种能力——一种高效组织、共享与复用代码的方法。

在本章中,我们将探索 Python 的导入(import)系统如何工作,如何创建并使用你自己的模块,以及如何将多个模块组织成包。我们还会研究特殊的 __name__ 变量,它让你编写的文件既能作为可导入的模块,也能作为独立脚本运行。

22.1) 模块是什么,以及 import 如何工作

理解模块

模块(module) 本质上就是一个包含定义与语句的 Python 文件。你创建的任何 .py 文件都是一个模块。当你在名为 calculator.py 的文件中编写一个函数时,这个文件就成为一个名为 calculator 的模块,其他 Python 文件可以使用它。

模块有几个重要作用:

  • 代码复用(Code Reusability):函数写一次,在多个程序中使用
  • 组织(Organization):将相关功能分组在一起
  • 命名空间管理(Namespace Management):保持名称彼此分离以避免冲突
  • 可维护性(Maintainability):更小、更聚焦的文件更易理解与修改

让我们创建一个简单的模块来看看它如何工作。创建一个名为 greetings.py 的文件:

python
# greetings.py
def say_hello(name):
    """Return a friendly greeting."""
    return f"Hello, {name}!"
 
def say_goodbye(name):
    """Return a farewell message."""
    return f"Goodbye, {name}. See you soon!"
 
# 一个模块级变量
default_greeting = "Welcome"

这个文件现在就是一个模块。它包含两个函数和一个变量,其他 Python 文件可以使用它们。

import 语句

要使用模块中的代码,你需要导入(import)它。import 语句告诉 Python 加载一个模块并使其内容可用。在同一目录下再创建一个名为 main.py 的文件:

python
# main.py
import greetings
 
message = greetings.say_hello("Alice")
print(message)  # Output: Hello, Alice!
 
farewell = greetings.say_goodbye("Bob")
print(farewell)  # Output: Goodbye, Bob. See you soon!
 
print(greetings.default_greeting)  # Output: Welcome

当你运行 main.py 时,Python 会执行 import greetings 语句。幕后发生了如下过程:

import greetings

greetings
是否已导入?

搜索 greetings.py

从上到下执行 greetings.py

创建名为 'greetings'
的模块对象

将函数与变量
存为属性

在当前命名空间中
提供 'greetings'

使用已有的
模块对象

重要:Python 只会在程序中第一次导入模块时执行该模块的代码。在同一程序中后续再次导入会复用已经加载的模块。这可以防止重复执行并节省时间。

访问模块内容

导入模块后,你可以使用点号表示法(dot notation)来访问其内容:module_name.item_name。这类似于我们在第 5 章和第 14 章学到的字符串方法(如 text.upper())或列表(list)方法(如 numbers.append())的访问方式。

python
import greetings
 
# 访问函数
result = greetings.say_hello("Charlie")
 
# 访问变量
greeting = greetings.default_greeting
 
# 你甚至可以查看模块里有什么
print(dir(greetings))  # 列出模块中定义的所有名称

点号表示法能清楚地表明每个名称来自哪里。当你看到 greetings.say_hello() 时,你会立刻知道这个函数来自 greetings 模块。

模块搜索路径

当你写 import greetings 时,Python 如何找到 greetings.py?Python 会按特定顺序搜索模块:

  1. 当前目录(Current Directory):你正在运行的脚本所在的目录
  2. PYTHONPATHPYTHONPATH 环境变量中列出的目录(如果设置了)
  3. 标准库(Standard Library):Python 内置模块目录
  4. site-packages:通过 pip 安装的第三方包

你可以通过查看 sys.path 来了解 Python 的搜索路径:

python
import sys
 
for path in sys.path:
    print(path)

Output (example - your actual paths will differ based on your system and Python installation):

/home/user/projects/myproject
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/usr/local/lib/python3.11/site-packages

输出中的第一个路径是当前工作目录。Python 会优先搜索该目录,因此它可以找到同一目录下的模块。

模块名与文件名

模块名就是不带 .py 扩展名的文件名。如果你的文件是 string_utils.py,模块名就是 string_utils。模块名必须遵循 Python 的标识符规则(我们在第 3 章学过):

  • 以字母或下划线开头
  • 只包含字母、数字和下划线
  • 不能是 Python 关键字
python
# 有效的模块名(以及文件名)
import data_processor      # data_processor.py
import user_auth          # user_auth.py
import _internal_helpers  # _internal_helpers.py
 
# Invalid - would cause errors
# import 2d_graphics       # Can't start with digit
# import my-module         # Hyphens not allowed
# import class             # 'class' is a keyword

常见陷阱:遮蔽标准库模块

要小心不要把你的模块命名为与标准库模块相同的名字。如果你在项目目录里创建了一个名为 random.py 的文件,Python 将导入你的文件而不是标准库的 random 模块,从而导致令人困惑的错误:

python
# 你的文件:random.py
def my_function():
    return 42
 
# 项目中的另一个文件
import random
print(random.randint(1, 6))  # ERROR! Your random.py doesn't have randint()

为了避免这种情况,在创建模块前先检查该名称是否已被标准库使用。你可以在 Python 交互式 shell 中尝试导入该名称。如果它能无错误导入,则说明该名称已经被占用。

导入期间会发生什么

让我们看看导入模块时实际发生了什么。创建一个名为 demo_module.py 的文件:

python
# demo_module.py
print("Module is being loaded!")
 
def greet():
    print("Hello from demo_module")
 
print("Module loading complete!")

现在导入它:

python
# test_import.py
print("Before import")
import demo_module
print("After import")
 
demo_module.greet()

Output:

Before import
Module is being loaded!
Module loading complete!
After import
Hello from demo_module

注意,demo_module.py 里的 print() 语句会在导入时执行。这说明导入模块会运行它的所有顶层代码。函数定义会被保存以供后续使用,但任何位于函数之外的代码都会立刻执行。

如果你在同一程序中再次导入同一个模块,加载信息不会再次出现:

python
import demo_module  # First import - executes module code
import demo_module  # Second import - uses cached module
import demo_module  # Third import - still uses cached module

Output:

Module is being loaded!
Module loading complete!

模块代码只会运行一次,无论你导入多少次。

22.2) 不同的导入方式:import、from 与 as

Python 提供了多种导入模块及其内容的方式。每种方式对你如何访问导入名称以及它们如何影响命名空间都有不同影响。

基本 import 语句

我们已经见过的基本 import 语句会加载整个模块:

python
import math
 
result = math.sqrt(16)
print(result)  # Output: 4.0
 
pi_value = math.pi
print(pi_value)  # Output: 3.141592653589793

使用这种方式时,你总是以模块名作为前缀。这使代码非常清晰——你总能看出一个名称来自哪里。

使用 from 导入特定名称

有时你只需要某个模块里的一两个项目。from 语句让你把特定名称直接导入到你的命名空间中:

python
from math import sqrt, pi
 
result = sqrt(25)  # No 'math.' prefix needed
print(result)  # Output: 5.0
 
print(pi)  # Output: 3.141592653589793

现在你可以直接使用 sqrtpi,无需 math. 前缀。当你频繁使用这些名称时,这种方式很方便。

让我们再用 greetings 模块看一个例子:

python
# Using from import
from greetings import say_hello
 
message = say_hello("Diana")  # Direct access
print(message)  # Output: Hello, Diana!
 
# However, say_goodbye is not available since we didn't import it
# say_goodbye("Diana")  # NameError: name 'say_goodbye' is not defined

你可以在一条语句中导入多个名称:

python
from greetings import say_hello, say_goodbye, default_greeting
 
print(say_hello("Eve"))      # Output: Hello, Eve!
print(say_goodbye("Frank"))  # Output: Goodbye, Frank!
print(default_greeting)      # Output: Welcome

通配符导入(以及为什么要避免)

Python 允许使用 * 从模块中导入所有内容:

python
from math import *
 
print(sqrt(9))   # Output: 3.0
print(cos(0))    # Output: 1.0
print(pi)        # Output: 3.141592653589793

这会导入模块中所有公开名称(不以下划线开头的名称)。虽然看起来很方便,但通常被认为是不良实践,因为:

  1. 命名空间污染(Namespace Pollution):你无法确切知道导入了哪些名称
  2. 名称冲突(Name Conflicts):导入的名称可能覆盖你自己的变量
  3. 可读性(Readability):读代码的人无法看出名称来自哪里
python
# 问题示例
from math import *
 
# Later in your code...
def sqrt(x):
    """Your own square root function."""
    return x ** 0.5
 
# Which sqrt are you using? Yours or math's?
result = sqrt(16)  # Confusing!

最佳实践:导入特定名称或使用基本 import 语句。避免使用 from module import *,除非是在交互式会话中做实验。

使用 as 重命名导入

有时模块或函数名很长,或者你想避免名称冲突。as 关键字可以让你创建别名(alias):

python
import math as m
 
result = m.sqrt(36)
print(result)  # Output: 6.0

这对名称较长的模块或遵循常见约定时特别有用:

python
import datetime as dt
 
today = dt.date.today()
print(today)  # Output: 2025-12-19 (or current date)

你也可以重命名特定导入项:

python
from math import sqrt as square_root
 
result = square_root(49)
print(result)  # Output: 7.0

当存在名称冲突时,这会很有帮助:

python
from math import sqrt as math_sqrt
 
def sqrt(x):
    """Custom square root with input validation."""
    if x < 0:
        return None
    return math_sqrt(x)
 
print(sqrt(25))   # Output: 5.0 (your function)
print(sqrt(-4))   # Output: None (your function)

混合使用导入风格

你可以在同一个文件中混用不同导入风格:

python
import math
from datetime import date, time
from random import randint as random_int
 
# Use math with prefix
radius = 5
area = math.pi * radius ** 2
 
# Use date and time directly
today = date.today()
current_time = time(14, 30)
 
# Use renamed function
dice_roll = random_int(1, 6)

选择合适的导入风格

下面是一个决策指南:

在以下情况使用 import module

  • 你需要模块中的多个项目
  • 你希望最大程度清晰地表明名称来源
  • 模块名简短且清晰

在以下情况使用 from module import name

  • 你只需要一两个特定项目
  • 名称具有辨识度且不太可能冲突
  • 你会频繁使用这些名称

在以下情况使用 import module as alias

  • 模块名非常长
  • 遵循常见约定(如 import numpy as np
  • 你需要避免与其他模块冲突

在生产代码中避免 from module import *

  • 只在交互式 shell 中做快速实验时使用
  • 永远不要在会被别人导入的模块中使用它

让我们看一个完整示例,展示良好的导入实践:

python
# data_processor.py
import math
from statistics import mean, median
from datetime import datetime as dt
 
def calculate_statistics(numbers):
    """Calculate various statistics for a list of numbers."""
    if not numbers:
        return None
    
    avg = mean(numbers)
    mid = median(numbers)
    std_dev = math.sqrt(sum((x - avg) ** 2 for x in numbers) / len(numbers))
    
    return {
        'mean': avg,
        'median': mid,
        'std_dev': std_dev,
        'timestamp': dt.now()
    }
 
# 测试函数
data = [10, 20, 30, 40, 50]
stats = calculate_statistics(data)
print(f"Mean: {stats['mean']}")      # Output: Mean: 30.0
print(f"Median: {stats['median']}")  # Output: Median: 30
print(f"Std Dev: {stats['std_dev']:.2f}")  # Output: Std Dev: 14.14

这个例子展示了:

  • import math 用于整个模块(我们后面可能会用到其他 math 函数)
  • from statistics import mean, median 用于我们频繁使用的特定函数
  • from datetime import datetime as dt 用于常见的别名用法

22.3) 常见 Python 标准库模块概览

Python 自带一个丰富的标准库(standard library)——一组为常见编程任务提供解决方案的模块。这些模块总是可用的;你不需要额外安装任何东西。了解标准库中有哪些内容能帮助你避免“重复造轮子”。

math 模块

math 模块提供了超出基本算术运算的数学函数:

python
import math
 
# 三角函数
angle_rad = math.radians(45)  # 将角度转换为弧度
print(math.sin(angle_rad))    # Output: 0.7071067811865476
print(math.cos(angle_rad))    # Output: 0.7071067811865475
 
# 舍入与绝对值
print(math.ceil(4.2))   # Output: 5 (round up)
print(math.floor(4.8))  # Output: 4 (round down)
print(math.fabs(-7.5))  # Output: 7.5 (absolute value as float)
 
# 指数与对数
print(math.exp(2))      # Output: 7.38905609893065 (e^2)
print(math.log(100))    # Output: 4.605170185988092 (natural log)
print(math.log10(100))  # Output: 2.0 (base-10 log)
 
# 常量
print(math.pi)  # Output: 3.141592653589793
print(math.e)   # Output: 2.718281828459045

正如我们在第 4 章学到的,math 模块对于高级数学运算至关重要。

random 模块

random 模块用于生成伪随机数并进行随机选择:

python
import random
 
# 随机整数
dice = random.randint(1, 6)  # Random integer from 1 to 6 (inclusive)
print(f"Dice roll: {dice}")
 
# 随机浮点数
probability = random.random()  # Random float from 0.0 to 1.0
print(f"Probability: {probability:.4f}")
 
# 从序列中随机选择
colors = ['red', 'blue', 'green', 'yellow']
chosen_color = random.choice(colors)
print(f"Chosen color: {chosen_color}")
 
# 原地打乱列表
deck = ['A', 'K', 'Q', 'J', '10']
random.shuffle(deck)
print(f"Shuffled deck: {deck}")
 
# 不放回随机抽样
lottery_numbers = random.sample(range(1, 50), 6)
print(f"Lottery numbers: {sorted(lottery_numbers)}")

Output (example - will vary due to randomness):

Dice roll: 4
Probability: 0.7382
Chosen color: green
Shuffled deck: ['Q', 'A', '10', 'K', 'J']
Lottery numbers: [7, 15, 23, 31, 38, 42]

datetime 模块

datetime 模块用于处理日期与时间:

python
from datetime import date, time, datetime, timedelta
 
# 当前日期与时间
today = date.today()
now = datetime.now()
print(f"Today: {today}")  # Output: Today: 2025-12-19
print(f"Now: {now}")      # Output: Now: 2025-12-19 14:30:45.123456
 
# 创建特定日期与时间
birthday = date(1990, 5, 15)
meeting_time = time(14, 30)
appointment = datetime(2025, 12, 25, 10, 0)
 
print(f"Birthday: {birthday}")          # Output: Birthday: 1990-05-15
print(f"Meeting: {meeting_time}")       # Output: Meeting: 14:30:00
print(f"Appointment: {appointment}")    # Output: Appointment: 2025-12-25 10:00:00
 
# 使用 timedelta 进行日期运算
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
print(f"Tomorrow: {tomorrow}")    # Output: Tomorrow: 2025-12-20
print(f"Next week: {next_week}")  # Output: Next week: 2025-12-26
 
# 提取组成部分
print(f"Year: {today.year}")      # Output: Year: 2025
print(f"Month: {today.month}")    # Output: Month: 12
print(f"Day: {today.day}")        # Output: Day: 19

os 模块

os 模块提供操作系统相关功能。我们将在第 26 章详细探讨它,不过这里先预览一下:

python
import os
 
# 当前工作目录
current_dir = os.getcwd()
print(f"Current directory: {current_dir}")
 
# 列出目录中的文件
files = os.listdir('.')
print(f"Files: {files[:3]}")  # Show first 3 files
 
# 检查路径是否存在
exists = os.path.exists('myfile.txt')
print(f"File exists: {exists}")
 
# 拼接路径组件(跨操作系统工作)
file_path = os.path.join('data', 'users', 'profile.txt')
print(f"Path: {file_path}")  # Output: data/users/profile.txt (or data\users\profile.txt on Windows)

sys 模块

sys 模块提供系统相关参数与函数:

python
import sys
 
# Python 版本信息
print(f"Python version: {sys.version}")
print(f"Version info: {sys.version_info}")
 
# 平台信息
print(f"Platform: {sys.platform}")  # Output: linux, darwin, win32, etc.
 
# 最大整数大小
print(f"Max int: {sys.maxsize}")

statistics 模块

statistics 模块提供统计计算函数:

python
import statistics
 
grades = [85, 92, 78, 90, 88, 95, 82]
 
# 集中趋势
avg = statistics.mean(grades)
mid = statistics.median(grades)
mode_val = statistics.mode([1, 2, 2, 3, 3, 3, 4])
 
print(f"Mean: {avg}")      # Output: Mean: 87.14285714285714
print(f"Median: {mid}")    # Output: Median: 88
print(f"Mode: {mode_val}") # Output: Mode: 3
 
# 离散程度
std_dev = statistics.stdev(grades)
variance = statistics.variance(grades)
 
print(f"Standard deviation: {std_dev:.2f}")  # Output: Standard deviation: 5.90
print(f"Variance: {variance:.2f}")           # Output: Variance: 34.81

collections 模块

collections 模块提供专门的容器类型。我们将在第 39 章更深入地探讨它,不过这里先感受一下:

python
from collections import Counter, defaultdict
 
# Counter - 统计出现次数
text = "hello world"
letter_counts = Counter(text)
print(letter_counts)  # Output: Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
print(letter_counts['l'])  # Output: 3
 
# defaultdict - 带默认值的字典
word_lists = defaultdict(list)
word_lists['fruits'].append('apple')
word_lists['fruits'].append('banana')
word_lists['vegetables'].append('carrot')
print(dict(word_lists))  # Output: {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']}

查找更多标准库模块

Python 标准库包含 200 多个模块。你可以通过多种方式探索它们:

python
# 查看所有可用模块(这会花一点时间)
help('modules')
 
# 获取某个特定模块的帮助
import math
help(math)
 
# 查看模块中有哪些内容
import random
print(dir(random))

Python 文档(https://docs.python.org/3/library/)为每个标准库模块提供了全面的信息。随着你经验的增长,你会逐渐发现哪些模块对你的工作最有用。

22.4) 创建并使用你自己的模块

创建你自己的模块很直接——任何 Python 文件都可以是模块。关键在于有条理地组织代码,使模块更聚焦、可复用且易理解。

创建一个简单模块

让我们创建一个用于处理学生成绩的模块。创建一个名为 grade_calculator.py 的文件:

python
# grade_calculator.py
"""Module for calculating and analyzing student grades."""
 
def calculate_average(grades):
    """Calculate the average of a list of grades."""
    if not grades:
        return 0
    return sum(grades) / len(grades)
 
def get_letter_grade(numeric_grade):
    """Convert a numeric grade to a letter grade."""
    if numeric_grade >= 90:
        return 'A'
    elif numeric_grade >= 80:
        return 'B'
    elif numeric_grade >= 70:
        return 'C'
    elif numeric_grade >= 60:
        return 'D'
    else:
        return 'F'
 
def find_highest(grades):
    """Find the highest grade in a list."""
    if not grades:
        return None
    return max(grades)
 
def find_lowest(grades):
    """Find the lowest grade in a list."""
    if not grades:
        return None
    return min(grades)
 
# 模块级常量
PASSING_GRADE = 60
HONOR_ROLL_THRESHOLD = 90

现在再创建另一个文件来使用这个模块:

python
# student_report.py
import grade_calculator
 
# 学生的测试成绩
test_scores = [85, 92, 78, 88, 95]
 
# 计算统计信息
average = grade_calculator.calculate_average(test_scores)
letter = grade_calculator.get_letter_grade(average)
highest = grade_calculator.find_highest(test_scores)
lowest = grade_calculator.find_lowest(test_scores)
 
# 生成报告
print("Student Grade Report")
print("=" * 40)
print(f"Test Scores: {test_scores}")
print(f"Average: {average:.1f}")
print(f"Letter Grade: {letter}")
print(f"Highest Score: {highest}")
print(f"Lowest Score: {lowest}")
 
# 检查是否进入荣誉榜
if average >= grade_calculator.HONOR_ROLL_THRESHOLD:
    print("Status: HONOR ROLL!")
elif average >= grade_calculator.PASSING_GRADE:
    print("Status: Passing")
else:
    print("Status: Needs Improvement")

Output:

Student Grade Report
========================================
Test Scores: [85, 92, 78, 88, 95]
Average: 87.6
Letter Grade: B
Highest Score: 95
Lowest Score: 78
Status: Passing

模块文档

注意 grade_calculator.py 顶部的 docstring。这个模块级 docstring 描述了模块的用途。当有人使用 help() 时,它会显示出来:

python
import grade_calculator
help(grade_calculator)

这会显示模块文档,包括模块 docstring 和所有函数 docstring。良好的文档会让你的模块更易使用。

模块级变量与常量

模块可以包含在模块的所有使用场景中共享的变量。它们常用于配置或常量:

python
# config.py
"""Application configuration settings."""
 
# Database settings
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "myapp"
 
# Application settings
MAX_LOGIN_ATTEMPTS = 3
SESSION_TIMEOUT = 1800  # seconds
DEBUG_MODE = False
 
# File paths
DATA_DIR = "/var/data"
LOG_DIR = "/var/log"
 
# Feature flags
ENABLE_CACHING = True
ENABLE_LOGGING = True

从模块中使用配置:

python
# app.py
import config
 
def connect_database():
    """Connect to the database using config settings."""
    print(f"Connecting to {config.DB_HOST}:{config.DB_PORT}")
    print(f"Database: {config.DB_NAME}")
    
    if config.DEBUG_MODE:
        print("DEBUG: Connection details logged")
 
def check_login_attempts(attempts):
    """Check if login attempts exceed the limit."""
    if attempts >= config.MAX_LOGIN_ATTEMPTS:
        print(f"Too many attempts! Maximum is {config.MAX_LOGIN_ATTEMPTS}")
        return False
    return True
 
connect_database()
print(check_login_attempts(2))  # Output: True
print(check_login_attempts(4))  # Output: Too many attempts! Maximum is 3

Output:

Connecting to localhost:5432
Database: myapp
True
Too many attempts! Maximum is 3

重要:模块级变量会在所有导入之间共享。如果你修改了某个模块变量,这个变化会影响所有使用该模块的代码:

python
# file1.py
import config
config.DEBUG_MODE = True
print(f"File1 - Debug mode: {config.DEBUG_MODE}")
 
# file2.py
import config
print(f"File2 - Debug mode: {config.DEBUG_MODE}")  # Will be True!

这种行为有时很有用,但也可能令人意外。修改模块级变量时要谨慎。

模块中的私有名称

按照约定,以单下划线开头的名称被视为模块的私有(private)内部(internal)内容:

python
# user_manager.py
"""Module for managing user accounts."""
 
# 私有辅助函数
def _validate_email(email):
    """Internal function to validate email format."""
    return '@' in email and '.' in email
 
# 公共函数
def create_user(username, email):
    """Create a new user account."""
    if not _validate_email(email):
        return None
    
    user = {
        'username': username,
        'email': email,
        'active': True
    }
    return user
 
# 私有常量
_MAX_USERNAME_LENGTH = 20
 
# 公共常量
MIN_PASSWORD_LENGTH = 8

当你使用 from user_manager import * 时,私有名称(以下划线开头的名称)不会被导入。不过如果有需要,你仍然可以显式访问它们:

python
import user_manager
 
# 公共函数 - 供使用的接口
user = user_manager.create_user("alice", "alice@example.com")
 
# 私有函数 - 可以访问,但不应依赖它
#(它可能在未来版本中发生变化)
is_valid = user_manager._validate_email("test@test.com")

下划线前缀是在向其他程序员传递一个信号:“这是实现细节。不要依赖它保持不变。”

22.5) 理解包与 __init__.py

随着项目增长,你会想把多个相关模块组织成一个包(package)。包是一个包含 Python 模块以及特殊 __init__.py 文件的目录。

什么是包?

包(package) 是一种将多个模块组织为层级结构的方式。你可以把它理解为一个包含 Python 文件的文件夹,而这个文件夹本身也可以被导入。

下面是一个简单的包结构:

myproject/
    main.py
    utilities/
        __init__.py
        text.py
        math.py
        file.py

在这个结构中,utilities 是一个包,包含三个模块:textmathfile__init__.py 文件(可以为空)告诉 Python:utilities 是一个包。

创建一个简单包

让我们创建一个用于数据处理的包。首先创建如下目录结构:

data_tools/
    __init__.py
    validators.py
    formatters.py

创建 validators.py

python
# data_tools/validators.py
"""Data validation functions."""
 
def is_valid_email(email):
    """Check if email has basic valid format."""
    return '@' in email and '.' in email.split('@')[1]
 
def is_valid_phone(phone):
    """Check if phone number has valid format (simple check)."""
    digits = ''.join(c for c in phone if c.isdigit())
    return len(digits) == 10
 
def is_positive_number(value):
    """Check if value is a positive number."""
    try:
        return float(value) > 0
    except (ValueError, TypeError):
        return False

创建 formatters.py

python
# data_tools/formatters.py
"""Data formatting functions."""
 
def format_phone(phone):
    """Format phone number as (XXX) XXX-XXXX."""
    digits = ''.join(c for c in phone if c.isdigit())
    if len(digits) != 10:
        return phone
    return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
 
def format_currency(amount):
    """Format number as currency."""
    return f"${amount:,.2f}"
 
def format_percentage(value, decimals=1):
    """Format number as percentage."""
    return f"{value * 100:.{decimals}f}%"

创建一个空的 __init__.py

python
# data_tools/__init__.py
"""Data processing tools package."""

从包中导入

现在你可以通过多种方式从包中导入:

python
# 方法 1:从包中导入模块
import data_tools.validators
 
email = "user@example.com"
is_valid = data_tools.validators.is_valid_email(email)
print(f"Email valid: {is_valid}")  # Output: Email valid: True
 
# 方法 2:使用 from 导入特定模块
from data_tools import formatters
 
phone = "1234567890"
formatted = formatters.format_phone(phone)
print(f"Formatted phone: {formatted}")  # Output: Formatted phone: (123) 456-7890
 
# 方法 3:导入特定函数
from data_tools.validators import is_valid_phone
from data_tools.formatters import format_currency
 
print(is_valid_phone("555-1234"))  # Output: False (not 10 digits)
print(format_currency(1234.56))    # Output: $1,234.56

__init__.py 文件

__init__.py 文件有两个用途:

  1. 将目录标记为包:Python 会把包含 __init__.py 的目录识别为包
  2. 包初始化代码__init__.py 中的代码会在首次导入该包时运行

__init__.py 文件可以为空,但也可以包含代码,让包更易使用:

python
# data_tools/__init__.py
"""Data processing tools package."""
 
# 将常用函数导入到包命名空间中
from data_tools.validators import is_valid_email, is_valid_phone
from data_tools.formatters import format_phone, format_currency
 
# 包版本
__version__ = '1.0.0'
 
# 包级常量
DEFAULT_CURRENCY_SYMBOL = '$'

现在用户可以直接从包中导入:

python
# Instead of: from data_tools.validators import is_valid_email
# You can write:
from data_tools import is_valid_email, format_currency
 
print(is_valid_email("test@test.com"))  # Output: True
print(format_currency(99.99))           # Output: $99.99

22.6) __name__ 变量与 if __name__ == "__main__"::将文件作为脚本运行

Python 文件可以承担两种用途:既可以被作为模块导入,也可以作为独立脚本运行。特殊的 __name__ 变量帮助你编写在两种场景下都能良好工作的代码。

理解 __name__

每个 Python 模块都有一个内置变量 __name__。Python 会根据文件的使用方式以不同方式设置该变量:

  • 被导入时__name__ 会被设为模块名
  • 直接运行时__name__ 会被设为 "__main__"

让我们看一个实际例子。创建一个名为 demo_name.py 的文件:

python
# demo_name.py
print(f"The __name__ variable is: {__name__}")

现在直接运行它:

bash
python demo_name.py

Output:

The __name__ variable is: __main__

现在从另一个文件中导入它:

python
# test_import.py
import demo_name

Output:

The __name__ variable is: demo_name

当你直接运行 demo_name.py 时,Python 会将 __name__ 设为 "__main__"。当你导入它时,Python 会将 __name__ 设为模块名("demo_name")。

if __name__ == "__main__": 模式

这种行为让你能编写只在文件被直接执行时运行、而在被导入时不运行的代码。这可以通过如下模式实现:

python
if __name__ == "__main__":
    # 这里的代码仅在文件被直接执行时运行
    pass

下面说明为什么这很有用。创建 math_utils.py

python
# math_utils.py
"""Utility functions for mathematical operations."""
 
def calculate_area(radius):
    """Calculate the area of a circle."""
    return 3.14159 * radius ** 2
 
def calculate_circumference(radius):
    """Calculate the circumference of a circle."""
    return 2 * 3.14159 * radius
 
# 测试代码 - 仅当文件被直接执行时运行
if __name__ == "__main__":
    print("Testing math_utils functions...")
    
    test_radius = 5
    area = calculate_area(test_radius)
    circumference = calculate_circumference(test_radius)
    
    print(f"Radius: {test_radius}")
    print(f"Area: {area:.2f}")
    print(f"Circumference: {circumference:.2f}")

当你直接运行这个文件时:

bash
python math_utils.py

Output:

Testing math_utils functions...
Radius: 5
Area: 78.54
Circumference: 31.42

但当你导入它时:

python
# use_math_utils.py
import math_utils
# 测试代码不会运行!
 
area = math_utils.calculate_area(10)
print(f"Area of circle: {area:.2f}")  # Output: Area of circle: 314.16

if __name__ == "__main__": 块中的测试代码在导入时不会执行。这使你可以在模块中包含测试代码、示例或演示,而不会影响导入该模块的代码。

if __name__ == "__main__": 的常见用途:

测试与演示

包含展示如何使用模块的示例:

python
# string_tools.py
def reverse_string(text):
    """Reverse a string."""
    return text[::-1]
 
def count_vowels(text):
    """Count vowels in text."""
    vowels = 'aeiouAEIOU'
    return sum(1 for char in text if char in vowels)
 
if __name__ == "__main__":
    # 演示代码
    sample = "Hello, World!"
    
    print(f"Original: {sample}")
    print(f"Reversed: {reverse_string(sample)}")
    print(f"Vowels: {count_vowels(sample)}")

在本章中,我们学习了如何使用模块与包来组织 Python 代码。我们探讨了导入系统如何工作、导入代码的不同方式,以及如何创建我们自己的模块与包。我们还学习了 __name__ 变量和 if __name__ == "__main__": 模式,它让文件既能作为可导入的模块,也能作为独立脚本运行。

随着程序规模增大,这些组织工具会变得越来越重要。在下一章中,我们将探索如何把函数当作数据来使用,并应用一些简单的函数式编程(functional programming)技巧,在本章建立的代码组织基础之上继续深入。

© 2025. Primesoft Co., Ltd.
support@primesoft.ai