当孩子写的 Python 代码越来越长,把几百行逻辑全塞在一个文件里时,找错、修改都会变成一场灾难。
真正的编程高手,写大型项目时就像“搭乐高”:先把大玩具拆成各个独立的积木块(模块),做好每个积木块的功能,最后用胶水把它们拼在一起。这就是模块化编程!
今天咱们不删减任何核心代码,手把手带大家从“零”设计并实现一个完整的单词分析大项目,把模块设计、__name__、__all__、__init__.py和主函数调用的底层逻辑一次性讲透!
🧱 一、什么是模块化编程?
简单来说,模块化编程就是把一个复杂的大程序,拆分成多个独立的、可以重复使用的.py小模块。每个模块只干一件事,模块之间通过“接口”互相配合。
三大基本步骤:
1.设计框架:确定要分几个模块,每个模块里放什么函数(先不写细节,用pass占位)。
2.实现与测试:把每个模块里的代码写完,并单独测试确保没 bug。
4.主函数组装:在main.py主程序里把这些模块串联起来,完成最终目标。
📐 二、第一步:架构设计(写伪代码)
假设我们要开发一个六级单词分析系统,功能有两个:
1.找出特定前缀(比如un-或mis-)的单词。
2.分析这些单词的词性(名词、动词等),统计数量并展示结果。
我们可以先把项目拆成 3 个模块,在设计阶段只写函数名和注释,具体实现全用 pass占位:
########## 模块 1: mysearch.py (负责检索)
def mysearch(head, func, path):
# 根据开头字符串和文件路径,返回符合条件的单词列表
# 这是一个高阶函数,接收 func 函数对文件进行预处理
pass
########## 模块 2: myanalyse.py (负责分析)
def myanalyse(wordlist):
# 分析输入的单词列表,统计词性数量和具体单词内容
# 返回两个字典:一个存个数,一个存内容
pass
########## 模块 3: mydisplay.py (负责展示)
def mydisplay(*args):
# 格式化打印展示最终结果
pass
小贴士:如果你是项目的“架构师”,写完上面这段设计,你的框架设计工作就完成了!接下来的具体实现可以分工给其他人写。
📂 三、项目文件目录规划
在动手写代码前,我们先在电脑里建立如下的文件夹(包)结构:
project6/
├── mydic/ # Python 包(文件夹)
│ ├── __init__.py # 包的标识与初始化文件
│ ├── design.py # 模块设计伪代码
│ ├── mysearch.py # 单词检索模块
│ ├── myanalyse.py # 词性分析模块
│ ├── mydisplay.py # 结果展示模块
│ └── 六级单词.txt # 数据资源文件
└── main.py # 项目主入口文件(放在包外面)
🔑 四、必知两大核心变量:__name__与__all__
在编写模块代码前,必须先搞懂 Python 模块自带的两个特殊变量:
1. __name__变量(模块测试神器)
当你直接运行当前.py文件时,__name__的值固定为'__main__'。
当这个.py文件被别人导入(import)时,__name__的值就是文件名。
直观对比示例:
# test1.py
print('test1 里的 __name__ 是:', __name__)
# test2.py
import test1 as t1
print('test2 里的 __name__ 是:', __name__)
如果直接运行test1.py,会打印:test1 里的 __name__ 是: __main__
如果直接运行test2.py,会打印:
test1 里的 __name__ 是: test1
test2 里的 __name__ 是: __main__
我们就利用if __name__ == '__main__':来编写模块的独立测试代码,这样既能在开发时测试模块,又不会在被导入时触发测试!
2. __all__变量(导出白名单)
__all__是一个列表,用来规定使用from 模块 import *时,允许暴露给外部使用的函数或变量。没有写在列表里的函数将被“隐藏”。
🛠️ 五、 第二步:模块的具体实现与测试
接下来,我们把刚才设计的 3 个模块一个个敲出真实代码:
1. 实现 mysearch.py(搜索模块)
这里包含文件检测函数myreadcheck和主搜索函数mysearch。
def mysearch(head='un', func=myreadcheck, path='六级单词.txt'):
'''高阶函数:根据前缀检索单词'''
check_res = func(path)
if check_res is None: # 文件不存在直接返回
return
else:
file = check_res[1] # 从元组中取出打开的文件对象
pattern = re.compile(r'\b{}.*\b'.format(head)) # 正则匹配:以 head 开头的单词
pattern2 = re.compile(r' +') # 正则匹配:多个连续空格
wordlist = []
for line in file:
res2 = pattern2.sub(' ', line) # 把多个空格压成 1 个空格
res1 = pattern.search(res2)
if res1 is not None:
wordlist.append(res1.group()) # 存入匹配到的单词
file.close() # 记得关闭文件
return wordlist
# 限定对外暴露的函数(隐藏内部辅助函数 myreadcheck)
__all__ = ['mysearch']
# 模块独立单元测试
if __name__ == '__main__':
print("【测试 mysearch 模块】检索 mis 开头的单词:")
print(mysearch(head='mis'))
2. 实现 myanalyse.py(词性分析模块)
# coding=gbk
'''模块 mysearch: 负责检查文件并检索特定前缀的单词'''
import re
def myreadcheck(path='六级单词.txt'):
'''检查文件是否存在,防止程序崩溃'''
try:
file = open(path)
return path, file # 返回多个变量时,Python 会打包成元组返回
except FileNotFoundError:
print('路径 {} 有错,未找到文件!'.format(path))
return None
# coding=gbk
'''模块 myanalyse: 主要用于对英文单词列表进行处理并进行词性分析'''
def myanalyse(wordlist=[]):
'''分析单词词性,返回两个字典(数量统计字典,单词列表字典)'''
wdic1 = {'名词': 0, '动词': 0, '形容词': 0, '介词': 0}
wdic2 = {'名词': [], '动词': [], '形容词': [], '介词': []}
for string in wordlist:
if 'n.' in string: # 根据文本中的词性标注进行分类
wdic1['名词'] += 1
wdic2['名词'].append(string)
if 'a.' in string:
wdic1['形容词'] += 1
wdic2['形容词'].append(string)
if 'vi.' in string or 'vt.' in string:
wdic1['动词'] += 1
wdic2['动词'].append(string)
if 'prep.' in string:
wdic1['介词'] += 1
wdic2['介词'].append(string)
return wdic1, wdic2
# 模块独立单元测试
if __name__ == '__main__':
print("【测试 myanalyse 模块】:")
dict1, dict2 = myanalyse(['good a.好的'])
print(dict1)
print(dict2)
3. 实现 mydisplay.py(结果展示模块)
# coding=gbk
'''模块 mydisplay: 用来格式化展示检索与分析的结果'''
def mydisplay(*args): # *args 可接收任意数量的参数并打包成元组
'''如果传入的是两个字典,逐行对比打印;否则直接打印'''
if len(args) == 2 and isinstance(args[0], dict) and isinstance(args[1], dict):
for key in args[0]:
print(key, "数量:", args[0][key], "列表:", args[1][key])
else:
print(args)
# 模块独立单元测试
if __name__ == '__main__':
print("【测试 mydisplay 模块】:")
mydisplay('test')
🔗 六项第三步:胶水文件__init__.py与主程序main.py
各个模块单独写好并测试通过后,怎么把它们装进“包”里并方便调用呢?
1. 设计 __init__.py
__init__.py是 Python 包的灵魂。当我们导入 mydic文件夹时,系统会首先自动执行这个文件。
在mydic/__init__.py里写入:
# 规定使用 from mydic import * 时导入的模块
__all__ = ['mysearch', 'myanalyse', 'mydisplay']
# 自动把包内子模块的函数暴露出来
from mydic.mysearch import *
from mydic.myanalyse import *
from mydic.mydisplay import *
这样处理后,外部调用者就不需要知道里面细分了多少个.py文件,直接导入mydic就能用!
2. 编写总控主程序 main.py
在mydic包外面新建main.py,调用我们搭好的所有积木:
import mydic # 导入我们自己写的 mydic 包
# 1. 检索以 mis 开头的单词
word = mydic.mysearch(head='mis', path='mydic\\六级单词.txt')
print("--- 原始检索到的单词列表 ---")
# *word 是星号解构语法:把列表解构成独立的参数传给函数
mydic.mydisplay(*word)
print("\n--- 词性分析汇总结果 ---")
# myanalyse 返回两个字典组成的元组,* 打碎元组后正好作为两个字典参数传给 mydisplay
mydic.mydisplay(*mydic.myanalyse(word))
📝 模块化编程知识点速查
概念/变量 | 核心作用大白话解释 |
模块化设计 | 大工程拆成小文件,用pass占位写框架,后期分工实现。 |
__name__ | 区分是“直接运行”还是“被导入”。直接运行时值为 '__main__',常用于写测试代码。 |
__all__ | 导出名单限制。决定用from module import *时哪些函数能被外部拿到。 |
__init__.py | 文件夹变成“Python 包”的身份证,可以在里面做预导入,简化外部调用。 |
*args/ *seq | 函数定义时用来接收多个参数;调用函数时用来对列表/元组解构(打散)。 |