在 Python 中,函数与模块是实现代码复用与工程化组织的核心工具。函数将重复逻辑封装为可调用单元,模块则将相关函数、类等聚合为独立文件,二者共同解决代码冗余、维护困难等问题。从简单工具函数到复杂项目架构,函数与模块贯穿程序设计的全流程。本章系统解析函数的定义与参数机制、命名空间与作用域规则、模块与包的组织方式,结合工程实践提炼代码复用技巧,帮助读者构建模块化思维,为开发可维护、可扩展的程序奠定基础。
5.1 函数基础
函数是代码复用的基本单元,通过封装特定逻辑实现 “一次定义,多次调用”。掌握函数的定义规范、参数传递与返回值处理,是编写简洁代码的关键。
5.1.1 函数的定义与调用
Python 中使用def关键字定义函数,基本语法如下:
def 函数名(参数列表):
"""函数文档字符串(描述功能、参数、返回值)"""
# 函数体逻辑
return 返回值 # 可选,默认返回None
示例:定义并调用问候函数
# 场景:用户问候功能
def greet_user(username):
"""向指定用户显示问候语(用户名首字母大写)"""
print(f"Hello, {username.title()}!")
# 调用函数:传入实参'alice'
greet_user('alice') # 输出:Hello, Alice!
📌 重点提示:函数名需遵循 “小写字母 + 下划线” 规范(如greet_user),直观反映功能;文档字符串(三引号包裹)是良好实践,通过help(函数名)可查看,例如help(greet_user)会显示函数功能说明。
5.1.2 函数的参数类型
函数参数是外部与函数交互的接口,Python 支持多种参数形式,适应不同传递需求:
| 参数类型 | 含义 | 示例 | 结果 | 说明 |
|---|---|---|---|---|
| 位置参数 | 按顺序匹配形参 | describe_pet('dog', 'willie') | 描述狗 willie | 实参与形参顺序必须一致 |
| 关键字参数 | 按 “参数名 = 值” 传递 | describe_pet(animal_type='cat', pet_name='mimi') | 描述猫 mimi | 无需关注顺序,可读性更强 |
| 默认参数 | 定义时指定默认值 | def describe_pet(pet_name, animal_type='dog') | 未传类型时默认为狗 | 需放在参数列表末尾 |
*args | 接收任意位置参数 | make_pizza(12, '蘑菇', '青椒') | 制作 12 寸双配料披萨 | 打包为元组,适合不定数量参数 |
**kwargs | 接收任意关键字参数 | build_profile('albert', 'einstein', location='princeton') | 构建含 location 的档案 | 打包为字典,适合扩展信息 |
示例 1:位置参数与关键字参数
# 场景:宠物信息描述
def describe_pet(animal_type, pet_name):
"""描述宠物的类型和名字"""
print(f"I have a {animal_type}. Its name is {pet_name.title()}.")
# 位置参数:按顺序传递
describe_pet('dog', 'willie') # 输出:I have a dog. Its name is Willie.
# 关键字参数:明确参数名
describe_pet(pet_name='mimi', animal_type='cat') # 输出:I have a cat. Its name is Mimi.
示例 2:默认参数
# 场景:简化常见调用(默认宠物类型为狗)
def describe_pet(pet_name, animal_type='dog'):
"""描述宠物,默认类型为狗"""
print(f"I have a {animal_type}. Its name is {pet_name.title()}.")
describe_pet('willie') # 未传类型,使用默认值 → I have a dog. Its name is Willie.
describe_pet('mimi', 'cat') # 传递类型,覆盖默认值 → I have a cat. Its name is Mimi.
示例 3:可变参数(*args与**kwargs)
# 场景1:处理不定数量的披萨配料
def make_pizza(size, *toppings):
"""制作指定尺寸和配料的披萨"""
print(f"Making a {size}-inch pizza with:")
for topping in toppings:
print(f"- {topping}")
make_pizza(12, '蘑菇', '青椒', '芝士') # 1个固定参数+3个可变参数
# 场景2:构建含扩展信息的用户档案
def build_profile(first, last, **user_info):
"""构建包含基本信息和扩展信息的用户档案"""
profile = {'first': first, 'last': last}
profile.update(user_info) # 添加扩展信息
return profile
user = build_profile('albert', 'einstein', location='princeton', field='physics')
print(user) # 输出:{'first': 'albert', 'last': 'einstein', 'location': 'princeton', 'field': 'physics'}
5.1.3 函数的返回值
函数通过return语句返回结果,支持单个值、多个值或复杂数据结构,满足不同场景的数据传递需求。
示例 1:返回单个值
# 场景:格式化姓名
def get_full_name(first, last):
"""返回首字母大写的全名"""
return f"{first.title()} {last.title()}"
name = get_full_name('jimi', 'hendrix')
print(name) # 输出:Jimi Hendrix
示例 2:返回多个值(本质为元组)
# 场景:返回姓名及长度
def get_name_info(first, last):
"""返回全名和名字长度"""
full_name = f"{first} {last}"
return full_name.title(), len(full_name) # 隐式返回元组
name, length = get_name_info('jimi', 'hendrix')
print(f"Name: {name}, Length: {length}") # 输出:Name: Jimi Hendrix, Length: 12
示例 3:返回复杂结构(字典 / 列表)
# 场景:生成学生成绩字典
def get_student_scores():
"""返回包含3名学生成绩的字典"""
return {
'alice': 95,
'bob': 88,
'charlie': 92
}
scores = get_student_scores()
for name, score in scores.items():
print(f"{name.title()}: {score}") # 依次输出学生成绩
5.1.4 函数文档字符串
文档字符串(Docstring)是函数的 “说明书”,位于函数定义首行,用三引号包裹,用于描述功能、参数、返回值及异常,提升代码可读性。
示例:规范的函数文档
def calculate_area(radius):
"""计算圆的面积
参数:
radius (float): 圆的半径(非负数)
返回:
float: 圆的面积(公式:π × radius²)
异常:
ValueError: 若radius为负数,抛出值错误
"""
import math
if radius < 0:
raise ValueError("半径不能为负数")
return math.pi * (radius **2)
# 查看文档
help(calculate_area) # 输出上述文档内容
print(calculate_area(5)) # 输出:78.53981633974483
5.1.5 匿名函数与高阶函数
除常规函数外,Python 支持匿名函数(lambda)与高阶函数,为简洁处理迭代逻辑提供灵活性。
匿名函数(lambda)
匿名函数是 “一行式” 函数,用lambda定义,仅含一个表达式,适合简单逻辑。语法:lambda 参数: 表达式。
示例:匿名函数的应用
# 场景1:简单计算
add = lambda x, y: x + y
print(add(3, 5)) # 输出:8
# 场景2:列表排序依据
students = [('Alice', 22), ('Bob', 19), ('Charlie', 25)]
students.sort(key=lambda x: x[1]) # 按年龄排序
print(students) # 输出:[('Bob', 19), ('Alice', 22), ('Charlie', 25)]
# 场景3:筛选数据
numbers = [1, 2, 3, 4, 5]
odds = list(filter(lambda x: x % 2 != 0, numbers)) # 筛选奇数
print(odds) # 输出:[1, 3, 5]
📌 重点提示:lambda仅适用于简单逻辑,复杂逻辑(如多分支、循环)需用def定义常规函数;匿名函数无文档,可读性较低,避免在核心逻辑中过度使用。
高阶函数
高阶函数指 “接收函数作为参数” 或 “返回函数” 的函数,Python 内置map、filter、reduce等,简化迭代处理。
| 函数 | 功能 | 示例 | 结果 |
|---|---|---|---|
map(func, iterable) | 对可迭代对象每个元素应用func | map(str, [1,2,3]) | ['1', '2', '3'] |
filter(func, iterable) | 保留func返回True的元素 | filter(lambda x: x>3, [1,2,3,4]) | [4] |
reduce(func, iterable) | 累积计算(需从functools导入) | reduce(lambda x,y: x+y, [1,2,3]) | 6 |
示例:高阶函数的应用
# 场景1:批量转换数据类型
numbers = [1, 2, 3, 4]
str_numbers = list(map(str, numbers)) # 转为字符串列表
print(str_numbers) # 输出:['1', '2', '3', '4']
# 场景2:筛选符合条件的元素
words = ['apple', 'cat', 'banana', 'dog']
long_words = list(filter(lambda s: len(s) > 3, words)) # 筛选长单词
print(long_words) # 输出:['apple', 'banana']
# 场景3:累积计算
from functools import reduce
total = reduce(lambda x, y: x + y, [1, 2, 3, 4]) # 求和
print(total) # 输出:10
5.2 命名空间与作用域
变量的可访问范围由 “作用域” 决定,而 “命名空间” 是变量名与值的映射集合。理解二者规则,可避免变量冲突,清晰管理变量访问范围。
5.2.1 命名空间概述
命名空间是 “变量名→值” 的字典,用于区分不同位置的变量,避免命名冲突。Python 主要有 3 类命名空间:
| 命名空间 | 含义 | 生命周期 | 示例 |
|---|---|---|---|
| 内置命名空间 | Python 自带的变量 / 函数 | 程序启动→退出 | print、len、math |
| 全局命名空间 | 模块顶层定义的变量 / 函数 | 模块导入→解释器退出 | 脚本中定义的a=10、def demo() |
| 局部命名空间 | 函数 / 类内部定义的变量 | 函数调用→执行结束 | 函数内的b=20 |
示例:命名空间的区分
# 全局命名空间:变量a、函数demo
a = 10
def demo():
# 局部命名空间:变量b
b = 20
print(f"局部b: {b}") # 可访问局部变量
print(f"全局a: {a}") # 可访问全局变量
demo()
print(f"全局a: {a}")
# print(b) # 报错:NameError(b不在全局命名空间)
5.2.2 作用域类型
作用域是命名空间的 “可见范围”,变量查找遵循 “LEGB” 规则(从内到外):
| 作用域 | 含义 | 查找优先级 |
|---|---|---|
| L(Local) | 函数 / 类内部 | 最高 |
| E(Enclosing) | 嵌套函数的外层函数 | 次之 |
| G(Global) | 模块顶层 | 再次之 |
| B(Built-in) | Python 内置 | 最低 |
示例:作用域的查找顺序
x = 100 # 全局作用域(G)
def outer():
x = 200 # 嵌套作用域(E)
def inner():
# 局部作用域(L)无x,向上查找E
print(f"inner中的x: {x}") # 输出:200
inner()
outer()
print(f"全局x: {x}") # 输出:100
5.2.3 作用域关键字(global与nonlocal)
默认情况下,函数内部无法修改全局或嵌套外层变量,需用关键字声明:
| 关键字 | 作用 | 适用场景 |
|---|---|---|
global | 声明变量为全局变量 | 函数内修改全局变量 |
nonlocal | 声明变量为嵌套外层变量 | 嵌套函数内修改外层变量 |
示例 1:global修改全局变量
x = 10
def modify_global():
global x # 声明x为全局变量
x = 20 # 修改全局变量
modify_global()
print(x) # 输出:20(全局变量已被修改)
示例 2:nonlocal修改嵌套外层变量
def outer():
x = 10 # 嵌套外层变量
def inner():
nonlocal x # 声明x为嵌套外层变量
x = 20 # 修改外层变量
inner()
print(x) # 输出:20(外层变量已被修改)
outer()
📌 重点提示:滥用global会增加代码耦合度,建议优先通过 “参数传递 + 返回值” 修改数据;nonlocal仅适用于嵌套函数,且变量必须在外层已定义。
5.3 模块与包
当代码量增长时,需用模块(单个.py 文件)与包(含多个模块的文件夹)组织代码,实现模块化拆分与复用。
5.3.1 模块基础与导入方式
模块是包含 Python 代码的.py 文件,通过import导入后可使用其内容。常用导入方式如下:
| 导入方式 | 语法 | 特点 |
|---|---|---|
| 导入整个模块 | import 模块名 | 使用时需加模块名.前缀 |
| 导入特定内容 | from 模块名 import 内容 | 可直接使用名称,无需前缀 |
| 导入并指定别名 | import 模块名 as 别名 | 简化长模块名调用 |
| 导入所有内容(不推荐) | from 模块名 import * | 易引发名称冲突 |
示例:模块导入方式
# 方式1:导入整个模块
import math
print(math.pi) # 输出:3.141592653589793
# 方式2:导入特定内容
from math import pi, sqrt
print(pi) # 输出:3.141592653589793
print(sqrt(16)) # 输出:4.0
# 方式3:指定别名
import numpy as np
print(np.array([1, 2, 3])) # 输出:[1 2 3]
模块缓存机制
Python 会将已导入的模块缓存到sys.modules中,同一模块仅导入一次,避免重复执行初始化代码。
示例:验证模块缓存
import sys
import string_utils # 首次导入,执行模块代码
# 查看缓存
print('string_utils' in sys.modules) # 输出:True
import string_utils # 二次导入,使用缓存,不执行代码
📌 提示:调试时需重新加载模块,可使用importlib.reload(模块名),但生产环境应避免频繁使用。
导入自定义模块
步骤 1:创建模块string_utils.py
# string_utils.py
def reverse_string(s):
"""反转字符串"""
return s[::-1]
def count_vowels(s):
"""统计元音字母数量"""
vowels = {'a', 'e', 'i', 'o', 'u'}
return sum(1 for c in s.lower() if c in vowels)
步骤 2:导入并使用
import string_utils as su
s = "Hello Python"
print(su.reverse_string(s)) # 输出:nohtyP olleH
print(su.count_vowels(s)) # 输出:4
5.3.2 包的结构与使用
包是含__init__.py的文件夹,用于组织多个相关模块。__init__.py可控制包的导入行为(如通过__all__指定公开模块)。
包结构示例
my_project/
├── main.py # 主程序
└── data/ # 数据处理包
├── __init__.py # 包初始化文件
├── clean.py # 数据清洗模块
└── analyze.py # 数据分析模块
配置__init__.py
# data/__init__.py
# 指定“from data import *”时导入的模块
__all__ = ['clean', 'analyze']
# 提前导入子模块,简化调用
from . import clean, analyze
导入包内模块
# 方式1:导入子模块并使用
from data import clean, analyze
data = [1, None, 3, 5]
cleaned = clean.remove_null(data) # 调用clean模块函数
avg = analyze.calculate_average(cleaned) # 调用analyze模块函数
print(avg) # 输出:3.0
# 方式2:直接导入函数
from data.clean import remove_null
from data.analyze import calculate_average
cleaned = remove_null([1, None, 3])
print(calculate_average(cleaned)) # 输出:2.0
5.3.3 常用标准库应用
Python 标准库是内置模块集合,涵盖文件操作、数学计算等功能,无需安装即可使用。
1. os:操作系统交互
用于文件路径处理、目录操作等跨平台功能。
import os
# 获取当前目录
print(os.getcwd()) # 输出:当前工作目录路径
# 拼接路径(跨平台兼容)
path = os.path.join("data", "test.txt")
print(path) # 输出:data/test.txt(Linux/macOS)或 data\test.txt(Windows)
# 创建多级目录
os.makedirs("temp/logs", exist_ok=True) # exist_ok=True避免目录已存在时报错
2. datetime:日期时间处理
用于创建、计算、格式化日期时间。
from datetime import datetime, timedelta
# 获取当前时间并格式化
now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S")) # 输出:2024-10-04 15:30:00
# 计算3天后的时间
future = now + timedelta(days=3)
print(future.strftime("%Y-%m-%d")) # 输出:2024-10-07
3. random:随机数生成
用于生成随机数、随机选择等场景。
import random
# 生成1-10的随机整数
print(random.randint(1, 10)) # 输出:3(示例)
# 从列表随机选择元素
fruits = ['apple', 'banana', 'orange']
print(random.choice(fruits)) # 输出:banana(示例)
4. logging:日志记录
用于程序运行日志的分级记录(比print更强大)。
import logging
# 配置日志:输出到控制台和文件
logging.basicConfig(
level=logging.WARNING, # 仅记录WARNING及以上
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log'), # 写入文件
logging.StreamHandler() # 输出到控制台
]
)
logging.warning("这是警告日志") # 会被记录
logging.info("这是信息日志") # 不会被记录(级别低于WARNING)
5.3.4 第三方模块安装与使用
第三方模块需通过pip安装,如requests(HTTP 请求)、pandas(数据处理)等。
安装模块
pip install requests pandas # 安装最新版本
pip install requests==2.31.0 # 安装指定版本
使用示例
示例 1:requests发送 HTTP 请求
import requests
response = requests.get("https://api.github.com")
if response.status_code == 200:
data = response.json()
print(data['message']) # 输出:Hello Octocat!
示例 2:pandas处理 CSV 数据
import pandas as pd
# 读取CSV文件
df = pd.read_csv("data.csv")
# 查看前5行
print(df.head())
# 筛选分数>90的行
high_scores = df[df['score'] > 90]
# 保存结果
high_scores.to_csv("high_scores.csv", index=False)
5.4 函数与模块综合应用及工程实践
结合工程实践,合理设计函数与模块结构,可提升代码的可复用性与可维护性。
5.4.1 函数装饰器进阶
装饰器用于 “增强函数功能而不修改其代码”,支持带参数、多装饰器叠加等高级用法。
带参数的装饰器
import time
from functools import wraps
def log_execution(file_path='log.txt'):
"""带参数的装饰器:记录函数执行时间到指定文件"""
def decorator(func):
@wraps(func) # 保留原函数元信息
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
# 写入日志
with open(file_path, 'a') as f:
f.write(f"{func.__name__} 执行时间:{end-start:.2f}秒\n")
return result
return wrapper
return decorator
@log_execution('func_log.txt') # 指定日志文件
def slow_task(n):
time.sleep(n)
slow_task(1) # 执行后,func_log.txt会记录执行时间
多个装饰器叠加
# 装饰器1:计时
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} 耗时:{time.time()-start:.2f}秒")
return result
return wrapper
# 装饰器2:日志
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__},参数:{args}, {kwargs}")
return func(*args, **kwargs)
return wrapper
@timer # 外层装饰器,后执行
@log # 内层装饰器,先执行
def add(a, b):
return a + b
add(3, 5) # 输出:调用 add,参数:(3, 5), {} → add 耗时:0.00秒
5.4.2 工程实践:函数模块化设计
模块化设计遵循 “单一职责” 原则:一个函数只做一件事,一个模块只包含相关功能。
示例:数据处理项目结构
data_processing/
├── main.py # 主程序入口
├── data_clean.py # 数据清洗函数
├── data_analyze.py # 数据分析函数
└── utils.py # 通用工具函数
data_clean.py(数据清洗)
def remove_null(data, fill_value=None):
"""移除或填充空值"""
if fill_value is None:
return [x for x in data if x is not None]
return [x if x is not None else fill_value for x in data]
def remove_duplicates(data):
"""移除重复元素,保留顺序"""
seen = set()
return [x for x in data if x not in seen and not seen.add(x)]
data_analyze.py(数据分析)
def calculate_mean(data):
"""计算均值"""
if not data:
raise ValueError("数据不能为空")
return sum(data) / len(data)
def calculate_variance(data):
"""计算方差"""
mean = calculate_mean(data)
return sum((x - mean)** 2 for x in data) / len(data)
main.py(主程序)
from data_clean import remove_null, remove_duplicates
from data_analyze import calculate_mean, calculate_variance
# 数据处理流程
raw_data = [1, None, 3, 5, 3, None, 7]
cleaned_data = remove_duplicates(remove_null(raw_data))
print(f"清洗后数据:{cleaned_data}")
print(f"均值:{calculate_mean(cleaned_data)}")
print(f"方差:{calculate_variance(cleaned_data)}")
5.4.3 最佳实践总结
- 函数设计:单一职责,参数明确,文档完整,避免过长函数(建议不超过 50 行)。
- 模块组织:按功能划分模块,避免过大模块(建议不超过 300 行),核心逻辑与辅助逻辑分离。
- 命名规范:函数 / 模块名使用小写 + 下划线,直观反映功能(如
calculate_mean而非func1)。 - 依赖管理:第三方模块版本固定(如通过
requirements.txt),避免版本兼容问题。 - 可测试性:函数应易于单元测试(输入明确,输出可预期),避免依赖全局状态。
通过遵循这些实践,可构建出逻辑清晰、易于维护的 Python 程序,为后续扩展与协作奠定基础。