在 Python 编程世界中,函数是构建高效、可维护代码的基石。无论是简单的脚本编写还是复杂的大型项目开发,函数都扮演着 “代码积木” 的核心角色 —— 它将重复逻辑封装成独立单元,实现模块化编程,大幅提升代码复用率与可读性。本文将从函数的基础概念出发,逐步深入参数设计、作用域机制、高阶函数、装饰器等进阶特性,结合大量实战案例,带你彻底掌握 Python 函数的精髓。
一、Python 函数基础:理解 “代码封装” 的本质
1.1 为什么需要函数?—— 从 “重复代码” 到 “模块化”
在未接触函数时,我们常会写出大量重复代码。例如,计算两个数的加减乘除运算,若每次计算都重新编写逻辑:
# 计算10和5的四则运算(无函数版)
a = 10
b = 5
print("加法结果:", a + b)
print("减法结果:", a - b)
print("乘法结果:", a * b)
print("除法结果:", a / b)
# 计算20和8的四则运算(重复编写逻辑)
c = 20
d = 8
print("加法结果:", c + d)
print("减法结果:", c - d)
print("乘法结果:", c * d)
print("除法结果:", c / d)
上述代码中,四则运算的打印逻辑被重复编写,若需修改格式(如添加分隔符),需修改所有重复处,维护成本极高。
而用函数封装后,代码可优化为:
# 定义四则运算函数(封装重复逻辑)
def calculate(a, b):
print("-" * 15)
print("加法结果:", a + b)
print("减法结果:", a - b)
print("乘法结果:", a * b)
print("除法结果:", a / b)
print("-" * 15)
# 调用函数(复用逻辑)
calculate(10, 5) # 计算10和5
calculate(20, 8) # 计算20和8
只需修改calculate函数内部,即可统一所有调用处的格式,这就是函数的核心价值:一次封装,多次复用;一处修改,全局生效。
1.2 函数的定义与调用:Python 的 “函数语法”
在 Python 中,函数通过def关键字定义,基本语法如下:
def 函数名(参数列表):
"""函数文档字符串(可选,用于说明功能)"""
函数体(封装的逻辑)
return 返回值(可选,默认返回None)
核心要素解析:
- 函数名:遵循 Python 命名规范(小写字母 + 下划线,如user_info),需见名知意;
- 参数列表:函数接收的 “输入数据”,可无参数(如def hello():),也可多个参数;
- 文档字符串:用三引号包裹的说明文本,通过函数名.__doc__或help(函数名)查看,提升代码可读性;
- 函数体:缩进的代码块(Python 强制缩进,通常 4 个空格),实现具体逻辑;
- return 语句:指定函数的 “输出数据”,执行后立即退出函数,未定义则返回None。
示例:带文档字符串与返回值的函数
def get_circle_info(radius):
"""
计算圆的周长和面积
参数:
radius (float): 圆的半径(非负)
返回:
tuple: 包含周长(2πr)和面积(πr²)的元组
异常:
ValueError: 若半径为负数,抛出异常
"""
import math
if radius < 0:
raise ValueError("半径不能为负数")
circumference = 2 * math.pi * radius
area = math.pi * radius ** 2
return circumference, area # 返回多个值(本质是元组)
# 调用函数并接收返回值
circle1 = get_circle_info(5)
print("半径5的圆:周长={:.2f},面积={:.2f}".format(circle1[0], circle1[1]))
# 解包返回值(更简洁的用法)
circum2, area2 = get_circle_info(3)
print("半径3的圆:周长={:.2f},面积={:.2f}".format(circum2, area2))
# 查看函数文档
print("\n函数说明:")
print(get_circle_info.__doc__)
运行结果:
半径5的圆:周长=31.42,面积=78.54
半径3的圆:周长=18.85,面积=28.27
函数说明:
计算圆的周长和面积
参数:
radius (float): 圆的半径(非负)
返回:
tuple: 包含周长(2πr)和面积(πr²)的元组
异常:
ValueError: 若半径为负数,抛出异常
二、函数参数深度解析:灵活传递数据的关键
函数参数是 “输入数据” 的载体,Python 支持多种参数类型,灵活搭配可满足复杂场景需求。本节将系统讲解参数的分类与使用技巧,避免常见误区。
2.1 位置参数:按 “顺序” 传递的基础参数
位置参数(Positional Arguments)是最基础的参数类型,调用时需按函数定义的参数顺序传递值,且数量必须匹配。
示例:计算长方形面积(位置参数)
def rectangle_area(length, width):
"""计算长方形面积:长×宽"""
return length * width
# 正确调用:按顺序传递length=5,width=3
print(rectangle_area(5, 3)) # 输出15
# 错误调用1:参数数量不匹配(少传1个)
# print(rectangle_area(5)) # 报错:missing 1 required positional argument: 'width'
# 错误调用2:参数顺序颠倒(结果错误,无语法报错)
print(rectangle_area(3, 5)) # 输出15(巧合正确,若逻辑为“长-宽”则错误)
注意:位置参数的顺序至关重要,若参数语义相近(如length和width),颠倒后可能导致逻辑错误但无语法提示,需格外注意。
2.2 关键字参数:按 “名称” 传递的灵活参数
为解决位置参数 “顺序敏感” 的问题,Python 支持关键字参数(Keyword Arguments):调用时通过 “参数名 = 值” 的形式指定,无需遵守顺序。
示例:用关键字参数调用函数
def rectangle_area(length, width):
return length * width
# 纯关键字参数调用(顺序可颠倒)
print(rectangle_area(width=3, length=5)) # 输出15(正确)
# 混合调用(位置参数在前,关键字参数在后)
print(rectangle_area(5, width=3)) # 输出15(正确)
# 错误混合调用(关键字参数在前,位置参数在后)
# print(rectangle_area(length=5, 3)) # 报错:positional argument follows keyword argument
规则:调用函数时,位置参数必须在关键字参数之前,否则 Python 无法解析参数顺序。
2.3 默认参数:简化 “重复默认值” 的传递
若函数的某个参数在多数场景下取值固定,可定义为默认参数(Default Arguments):在函数定义时指定默认值,调用时可省略该参数(自动使用默认值)。
示例:计算商品折扣价(默认参数)
def calculate_discount(price, discount=0.9):
"""
计算折扣价:原价×折扣率
discount默认值为0.9(即9折)
"""
return price * discount
# 省略默认参数(使用9折)
print(calculate_discount(100)) # 输出90.0(100×0.9)
# 覆盖默认参数(使用8折)
print(calculate_discount(100, 0.8)) # 输出80.0
# 用关键字参数覆盖默认值
print(calculate_discount(100, discount=0.7)) # 输出70.0
默认参数的 “坑”:避免使用可变类型作为默认值
Python 函数的默认参数在函数定义时初始化一次,而非每次调用时重新初始化。若默认参数为可变类型(如列表、字典),多次调用可能导致意外结果。
反例(错误用法):
def add_item(item, items=[]):
"""往列表中添加元素(错误示例)"""
items.append(item)
return items
# 第一次调用:看似正常
print(add_item("apple")) # 输出['apple']
# 第二次调用:意外保留了上次的元素
print(add_item("banana")) # 输出['apple', 'banana'](预期应为['banana'])
原因:items=[]在函数定义时创建了一个列表对象,每次调用函数若未传递items,都会复用同一个列表,导致元素累积。
解决方案:用None作为默认值,在函数内部创建可变类型:
def add_item(item, items=None):
"""往列表中添加元素(正确示例)"""
if items is None: # 若未传递items,创建新列表
items = []
items.append(item)
return items
# 多次调用:每次都是新列表(无累积)
print(add_item("apple")) # 输出['apple']
print(add_item("banana")) # 输出['banana']
# 传递自定义列表(正常累积)
my_list = ["orange"]
print(add_item("grape", my_list)) # 输出['orange', 'grape']
2.4 可变参数:接收 “任意数量” 的参数
当函数需要处理不确定数量的参数时,可使用可变参数:*args(接收任意数量的位置参数)和**kwargs(接收任意数量的关键字参数)。
2.4.1 *args:打包位置参数为元组
*args中的*表示 “打包”,将调用时传递的多个位置参数打包成一个元组(tuple),函数内部可通过args访问该元组。
示例:计算任意多个数的总和
def sum_all(*args):
"""计算任意多个数的总和"""
print("接收的参数类型:", type(args)) # 输出<class 'tuple'>
total = 0
for num in args:
total += num
return total
# 传递任意数量的位置参数
print(sum_all(1, 2, 3)) # 输出6(1+2+3)
print(sum_all(10, 20, 30, 40)) # 输出100(10+20+30+40)
# 传递列表/元组:用*解包(将元素转为位置参数)
my_nums = [1, 2, 3, 4, 5]
print(sum_all(*my_nums)) # 输出15(等价于sum_all(1,2,3,4,5))
2.4.2 **kwargs:打包关键字参数为字典
**kwargs中的**表示 “键值对打包”,将调用时传递的多个关键字参数打包成一个字典(dict),函数内部可通过kwargs访问该字典(key 为参数名,value 为参数值)。
示例:打印用户的任意信息
def print_user_info(**kwargs):
"""打印用户的任意信息(如姓名、年龄、性别)"""
print("接收的参数类型:", type(kwargs)) # 输出<class 'dict'>
for key, value in kwargs.items():
print(f"{key}: {value}")
# 传递任意数量的关键字参数
print_user_info(name="张三", age=25, gender="男")
# 输出:
# name: 张三
# age: 25
# gender: 男
# 传递字典:用**解包(将键值对转为关键字参数)
user_dict = {"name": "李四", "age": 30, "city": "北京"}
print_user_info(**user_dict)
# 输出:
# name: 李四
# age: 30
# city: 北京
2.4.3 参数组合顺序:位置→默认→*args→关键字 - only→**kwargs
当函数同时使用多种参数时,需遵循固定的定义顺序,否则 Python 会报错。正确顺序为:
- 位置参数(如a, b)
- 默认参数(如c=0)
- *args(可变位置参数)
- 关键字 - only 参数(Python 3 新增,需在*args后,用*分隔或直接在*args后定义)
- **kwargs(可变关键字参数)
示例:多种参数组合的正确定义
def mix_params(a, b, c=0, *args, d, e=5, **kwargs):
"""多种参数组合的示例"""
print("位置参数a:", a)
print("位置参数b:", b)
print("默认参数c:", c)
print("可变位置参数args:", args)
print("关键字-only参数d:", d) # 必须通过关键字传递
print("关键字-only默认参数e:", e)
print("可变关键字参数kwargs:", kwargs)
# 正确调用(按顺序传递,关键字-only参数需显式指定)
mix_params(1, 2, 3, 4, 5, d=6, e=7, f=8, g=9)
# 输出:
# 位置参数a: 1
# 位置参数b: 2
# 默认参数c: 3
# 可变位置参数args: (4, 5)
# 关键字-only参数d: 6
# 关键字-only默认参数e: 7
# 可变关键字参数kwargs: {'f': 8, 'g': 9}
# 错误调用(d未通过关键字传递)
# mix_params(1,2,3,4,5,6,e=7) # 报错:missing 1 required keyword-only argument: 'd'
说明:关键字 - only 参数(如d)必须通过 “关键字 = 值” 的形式传递,无法通过位置传递,避免了参数顺序混淆的问题。
三、函数作用域:理解变量的 “可见范围”
在 Python 中,变量并非在所有地方都可访问,其 “可见范围” 称为作用域(Scope)。函数的作用域规则决定了变量在函数内外的访问权限,理解这一规则是避免变量冲突的关键。
3.1 作用域的分类:LEGB 规则
Python 遵循LEGB 规则查找变量,即从 “近” 到 “远” 依次查找,找到即停止:
- L(Local):局部作用域:函数内部定义的变量,仅在函数内部可访问;
- E(Enclosing):嵌套作用域:外层嵌套函数的作用域(如函数内定义的子函数);
- G(Global):全局作用域:模块(.py 文件)层面定义的变量,整个模块可访问;
- B(Built-in):内置作用域:Python 内置的变量 / 函数(如print、len、int),全局可访问。
示例:LEGB 规则的实际应用
# B(Built-in):内置变量(无需定义,直接使用)
print(len([1,2,3])) # 输出3(len是内置函数)
# G(Global):全局变量(模块层面定义)
global_var = "我是全局变量"
def outer_func():
# E(Enclosing):嵌套作用域变量(外层函数定义)
enclosing_var = "我是嵌套变量"
def inner_func():
# L(Local):局部变量(内层函数定义)
local_var = "我是局部变量"
# 查找变量:优先找局部(L)
print("inner_func中访问local_var:", local_var) # 输出“我是局部变量”
# 局部无该变量,找嵌套作用域(E)
print("inner_func中访问enclosing_var:", enclosing_var) # 输出“我是嵌套变量”
# 嵌套作用域无该变量,找全局(G)
print("inner_func中访问global_var:", global_var) # 输出“我是全局变量”
# 全局无该变量,找内置(B)
print("inner_func中访问内置len:", len("test")) # 输出4
inner_func() # 调用内层函数
outer_func()
# 错误:局部变量无法在函数外访问
# print(local_var) # 报错:name 'local_var' is not defined
# 错误:嵌套作用域变量无法在外部访问
# print(enclosing_var) # 报错:name 'enclosing_var' is not defined
3.2 全局变量与 global 关键字
默认情况下,函数内部无法修改全局变量(仅能读取),若需修改,需用global关键字声明变量为全局变量。
示例:修改全局变量(需用 global)
global_count = 0 # 全局变量
def add_count():
# 声明global_count为全局变量(否则视为局部变量)
global global_count
global_count += 1 # 修改全局变量
print("函数内:global_count =", global_count)
# 调用函数
add_count() # 输出“函数内:global_count = 1”
add_count() # 输出“函数内:global_count = 2”
# 函数外访问修改后的全局变量
print("函数外:global_count =", global_count) # 输出“函数外:global_count = 2”
注意:滥用global会破坏函数的 “封装性”(函数依赖外部变量,难以独立测试),除非必要,否则优先通过参数传递数据,而非直接操作全局变量。
3.3 嵌套作用域与 nonlocal 关键字
对于嵌套函数(函数内定义的子函数),内层函数默认无法修改外层函数的变量(仅能读取)。若需修改,需用nonlocal关键字声明变量为 “非局部变量”(即嵌套作用域变量)。
示例:修改嵌套作用域变量(需用 nonlocal)
def outer_func():
outer_count = 0 # 嵌套作用域变量(外层函数)
def inner_func():
# 声明outer_count为非局部变量(否则视为局部变量)
nonlocal outer_count
outer_count += 1 # 修改嵌套作用域变量
print("内层函数:outer_count =", outer_count)
return inner_func # 返回内层函数(闭包特性,后续讲解)
# 获取内层函数对象
counter = outer_func()
# 调用内层函数(多次调用可累积修改)
counter() # 输出“内层函数:outer_count = 1”
counter() # 输出“内层函数:outer_count = 2”
对比:global用于关联全局变量,nonlocal用于关联嵌套作用域变量(非全局、非局部),二者不可混用。
四、高阶函数与函数式编程:让函数成为 “数据”
Python 中,函数是 “一等公民”(First-Class Citizen),意味着函数可作为参数传递、作为返回值返回、赋值给变量或存储在容器中。基于这一特性,Python 支持函数式编程风格,而高阶函数是其核心载体。
4.1 什么是高阶函数?
高阶函数(Higher-Order Function)满足以下任一条件:
- 接收一个或多个函数作为参数;
- 返回一个函数作为结果。
示例 1:接收函数作为参数
# 定义“加法”和“乘法”函数
def add(a, b):
return a + b
def multiply(a, b):
return a * b
# 高阶函数:接收“运算函数”作为参数
def calculate(operation, x, y):
"""根据传入的运算函数,计算x和y的结果"""
return operation(x, y)
# 传递add函数作为参数(计算加法)
print(calculate(add, 3, 4)) # 输出7(3+4)
# 传递multiply函数作为参数(计算乘法)
print(calculate(multiply, 3, 4)) # 输出12(3×4)
示例 2:返回函数作为结果
def create_operation(operator):
"""根据运算符,返回对应的运算函数"""
def add(a, b):
return a + b
def multiply(a, b):
return a * b
if operator == "+":
return add # 返回add函数
elif operator == "*":
return multiply # 返回multiply函数
else:
raise ValueError("不支持的运算符")
# 获取加法函数
add_func = create_operation("+")
print(add_func(5, 6)) # 输出11(5+6)
# 获取乘法函数
multiply_func = create_operation("*")
print(multiply_func(5, 6)) # 输出30(5×6)
4.2 Python 内置高阶函数:高效处理数据
Python 标准库提供了多个常用高阶函数,如map、filter、reduce,用于简化数据处理逻辑(替代循环)。
4.2.1 map:对可迭代对象的每个元素应用函数
map(function, iterable)的作用:
- 对iterable(如列表、元组)中的每个元素,调用function处理;
- 返回一个迭代器(需转为列表 / 元组查看结果)。
示例:将列表中所有元素转为平方
# 定义平方函数
def square(x):
return x ** 2
# 原始列表
nums = [1, 2, 3, 4, 5]
# 用map处理:每个元素调用square
result = map(square, nums)
# 转为列表查看结果
print(list(result)) # 输出[1, 4, 9, 16, 25]
# 结合lambda(匿名函数,后续讲解)简化代码
print(list(map(lambda x: x ** 2, nums))) # 输出[1, 4, 9, 16, 25]
4.2.2 filter:筛选可迭代对象中满足条件的元素
filter(function, iterable)的作用:
- function返回布尔值(True/False);
- 保留iterable中使function返回True的元素;
- 返回一个迭代器(需转为列表 / 元组查看结果)。
示例:筛选列表中的偶数
# 定义“判断偶数”的函数
def is_even(x):
return x % 2 == 0
# 原始列表
nums = [1, 2, 3, 4, 5, 6]
# 用filter筛选偶数
result = filter(is_even, nums)
# 转为列表查看结果
print(list(result)) # 输出[2, 4, 6]
# 结合lambda简化代码
print(list(filter(lambda x: x % 2 == 0, nums))) # 输出[2, 4, 6]
4.2.3 reduce:对可迭代对象的元素累积计算
reduce(function, iterable[, initializer])的作用(需从functools导入):
- function接收两个参数,返回一个累积结果;
- 从左到右对iterable的元素依次应用function,最终得到一个单一结果;
- 可选initializer:初始值,若指定,先将初始值与第一个元素计算。
示例:计算列表中所有元素的乘积
# 导入reduce(Python 3中需显式导入)
from functools import reduce
# 定义“乘法累积”函数
def multiply(a, b):
return a * b
# 原始列表
nums = [1, 2, 3, 4, 5]
# 用reduce计算累积乘积
result = reduce(multiply, nums)
print(result) # 输出120(1×2×3×4×5)
# 结合lambda简化代码
print(reduce(lambda a, b: a * b, nums)) # 输出120
# 指定初始值(如初始值为2,计算2×1×2×3×4×5)
print(reduce(lambda a, b: a * b, nums, 2)) # 输出240
4.3 匿名函数 lambda:简化 “短逻辑” 函数
在使用高阶函数时,若函数逻辑简单(仅一行代码),可通过lambda定义匿名函数(无函数名),避免单独定义函数的繁琐。
语法:lambda 参数列表: 表达式
- lambda是关键字,无实际意义;
- 参数列表:与普通函数类似(如x、x, y);
- 表达式:函数的返回值(仅一行,无需return);
- 匿名函数无法包含复杂逻辑(如循环、条件判断需用三元表达式)。
示例:lambda 的常见用法
# 1. 定义简单的加法匿名函数
add = lambda x, y: x + y
print(add(3, 4)) # 输出7
# 2. 结合sorted排序(按元组的第二个元素排序)
students = [("张三", 25), ("李四", 20), ("王五", 28)]
# sorted的key参数接收函数,lambda返回排序依据(第二个元素)
sorted_students = sorted(students, key=lambda x: x[1])
print(sorted_students) # 输出[('李四', 20), ('张三', 25), ('王五', 28)]
# 3. 结合map处理数据(将列表元素转为小写)
words = ["PYTHON", "JAVA", "C++"]
lower_words = list(map(lambda x: x.lower(), words))
print(lower_words) # 输出['python', 'java', 'c++']
注意:lambda 仅适用于简单逻辑,复杂逻辑(如多分支判断、循环)需用普通函数定义,避免代码可读性下降。
五、装饰器:给函数 “动态添加功能” 的神器
装饰器(Decorator)是 Python 中最强大的特性之一,它本质是一个高阶函数,可在不修改原函数代码的前提下,为函数动态添加功能(如日志记录、性能计时、权限校验)。装饰器广泛应用于框架开发(如 Django、Flask)和业务代码中,是 “开闭原则”(对扩展开放,对修改关闭)的典型实现。
5.1 装饰器的基础原理:闭包 + 高阶函数
装饰器的核心依赖 “闭包”(Closure)特性:嵌套函数可记住并访问外层函数的变量(即使外层函数已执行完毕)。结合高阶函数的 “接收函数 + 返回函数” 能力,即可实现装饰器。
示例 1:最简单的装饰器(打印函数调用日志)
# 定义装饰器(高阶函数)
def log_decorator(func):
# 定义嵌套函数(闭包),接收任意参数
def wrapper(*args, **kwargs):
# 装饰器添加的功能:打印函数名和参数
print(f"调用函数:{func.__name__}")
print(f"位置参数:{args}")
print(f"关键字参数:{kwargs}")
# 调用原函数,并获取返回值
result = func(*args, **kwargs)
# 装饰器添加的功能:打印返回值
print(f"函数{func.__name__}的返回值:{result}")
return result # 返回原函数的结果
return wrapper # 返回嵌套函数(替代原函数)
# 用@语法使用装饰器(等价于:add = log_decorator(add))
@log_decorator
def add(a, b):
return a + b
# 调用被装饰后的add函数
result = add(3, 4)
# 输出:
# 调用函数:add
# 位置参数:(3, 4)
# 关键字参数:{}
# 函数add的返回值:7
print("最终结果:", result) # 输出“最终结果:7”
解析:
- @log_decorator是装饰器的语法糖,等价于add = log_decorator(add);
- log_decorator(add)接收add函数作为参数,返回wrapper函数;
- 后续调用add(3,4),实际调用的是wrapper(3,4);
- wrapper函数先执行装饰逻辑(打印日志),再调用原add函数,最后返回结果。
5.2 带参数的装饰器:让装饰器更灵活
若需为装饰器传递参数(如日志输出格式、超时时间),需在基础装饰器外再套一层 “参数接收函数”,形成 “三层嵌套” 结构。
示例 2:带参数的装饰器(自定义日志前缀)
# 第一层:接收装饰器参数
def log_decorator(prefix="INFO"):
# 第二层:接收原函数(基础装饰器)
def decorator(func):
# 第三层:包装原函数(闭包)
def wrapper(*args, **kwargs):
# 使用装饰器参数(prefix)
print(f"[{prefix}] 调用函数:{func.__name__}")
result = func(*args, **kwargs)
print(f"[{prefix}] 函数{func.__name__}执行完毕")
return result
return wrapper
return decorator
# 用法1:使用默认参数(prefix="INFO")
@log_decorator()
def add(a, b):
return a + b
# 用法2:传递装饰器参数(prefix="DEBUG")
@log_decorator(prefix="DEBUG")
def multiply(a, b):
return a * b
# 调用被装饰的函数
print(add(3, 4))
# 输出:
# [INFO] 调用函数:add
# [INFO] 函数add执行完毕
# 7
print(multiply(3, 4))
# 输出:
# [DEBUG] 调用函数:multiply
# [DEBUG] 函数multiply执行完毕
# 12
解析:
- @log_decorator(prefix="DEBUG")等价于multiply = log_decorator(prefix="DEBUG")(multiply);
- 第一层log_decorator(prefix="DEBUG")返回decorator函数;
- 第二层decorator(multiply)接收multiply函数,返回wrapper函数;
- 最终调用multiply(3,4),实际执行wrapper(3,4),并使用prefix="DEBUG"的日志格式。
5.3 装饰器的常见应用场景
装饰器的灵活性使其适用于多种场景,以下是几个典型案例:
场景 1:函数性能计时(统计执行时间)
import time
from functools import wraps
# 计时装饰器(保留原函数元信息,见5.4)
def time_decorator(func):
@wraps(func) # 关键:保留原函数的__name__、__doc__等信息
def wrapper(*args, **kwargs):
start_time = time.time() # 记录开始时间
result = func(*args, **kwargs) # 调用原函数
end_time = time.time() # 记录结束时间
print(f"函数{func.__name__}执行时间:{end_time - start_time:.4f}秒")
return result
return wrapper
# 测试:统计循环100万次的时间
@time_decorator
def loop_test():
total = 0
for i in range(1_000_000):
total += i
return total
loop_test() # 输出类似“函数loop_test执行时间:0.0456秒”
场景 2:权限校验(仅允许特定用户调用)
from functools import wraps
# 权限校验装饰器(接收允许的用户列表)
def auth_decorator(allowed_users):
def decorator(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if user in allowed_users:
print(f"用户{user}权限通过,执行函数{func.__name__}")
return func(user, *args, **kwargs)
else:
raise PermissionError(f"用户{user}无权限执行{func.__name__}")
return wrapper
return decorator
# 仅允许"admin"和"editor"调用
@auth_decorator(allowed_users=["admin", "editor"])
def delete_data(user, data_id):
print(f"用户{user}删除数据:{data_id}")
# 正确调用(admin有权限)
delete_data("admin", 1001) # 输出“用户admin权限通过...删除数据:1001”
# 错误调用(guest无权限)
try:
delete_data("guest", 1002)
except PermissionError as e:
print(e) # 输出“用户guest无权限执行delete_data”
5.4 装饰器的 “坑”:保留原函数元信息
默认情况下,装饰器返回的wrapper函数会覆盖原函数的元信息(如__name__、__doc__),导致help(函数名)或函数名.__name__获取的是wrapper的信息,而非原函数。
问题示例:
def log_decorator(func):
def wrapper(*args, **kwargs):
"""wrapper函数的文档字符串"""
print(f"调用函数:{func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_decorator
def add(a, b):
"""计算两个数的和"""
return a + b
# 查看函数名(预期是add,实际是wrapper)
print(add.__name__) # 输出“wrapper”
# 查看函数文档(预期是“计算两个数的和”,实际是“wrapper函数的文档字符串”)
print(add.__doc__) # 输出“wrapper函数的文档字符串”
解决方案:使用functools.wraps
functools.wraps是一个装饰器,可将原函数的元信息(__name__、__doc__、__module__等)复制到wrapper函数中,解决元信息丢失问题。
修复示例:
from functools import wraps # 导入wraps
def log_decorator(func):
# 用wraps装饰wrapper,保留原函数元信息
@wraps(func)
def wrapper(*args, **kwargs):
"""wrapper函数的文档字符串"""
print(f"调用函数:{func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_decorator
def add(a, b):
"""计算两个数的和"""
return a + b
# 查看函数名(正确显示add)
print(add.__name__) # 输出“add”
# 查看函数文档(正确显示原函数文档)
print(add.__doc__) # 输出“计算两个数的和”
建议:编写装饰器时,务必使用@wraps(func),避免元信息丢失导致的调试困难。
六、实战案例:用函数构建 “简易学生管理系统”
为综合应用前文所学的函数知识,本节将实现一个 “简易学生管理系统”,包含添加学生、查询学生、修改成绩、删除学生、统计分析五大功能,体现函数的模块化、复用性与扩展性。
6.1 系统需求与设计
核心需求:
- 数据存储:用字典存储学生信息(学号:{姓名、年龄、成绩});
- 功能模块:每个功能对应一个函数,主函数控制流程;
- 用户交互:命令行菜单,支持循环操作;
- 数据校验:添加 / 修改时校验学号唯一性、成绩范围(0-100)。
函数设计:
|
函数名 |
功能描述 |
参数 |
返回值 |
|
add_student() |
添加学生信息 |
学生字典、学号、信息 |
bool(成功 / 失败) |
|
query_student() |
查询学生信息(按学号 / 姓名) |
学生字典、查询类型、关键词 |
列表(匹配的学生) |
|
modify_score() |
修改学生成绩 |
学生字典、学号、新成绩 |
bool(成功 / 失败) |
|
delete_student() |
删除学生信息 |
学生字典、学号 |
bool(成功 / 失败) |
|
analyze_data() |
统计分析(平均分、最高分) |
学生字典 |
字典(统计结果) |
|
show_menu() |
显示主菜单 |
无 |
无 |
|
main() |
主函数(控制流程) |
无 |
无 |
6.2 完整代码实现
from functools import wraps
# ------------------------------
# 装饰器:统计函数执行时间(可选功能)
# ------------------------------
def time_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"\n[计时] 函数{func.__name__}执行时间:{end - start:.6f}秒")
return result
return wrapper
# ------------------------------
# 功能函数:添加学生
# ------------------------------
def add_student(students, student_id, name, age, score):
"""
添加学生信息到字典中
参数:
students (dict): 存储学生信息的字典(key:学号,value:学生信息字典)
student_id (str): 学号(唯一)
name (str): 姓名
age (int): 年龄
score (int): 成绩(0-100)
返回:
bool: True(添加成功),False(添加失败)
"""
# 校验学号是否已存在
if student_id in students:
print(f"错误:学号{student_id}已存在,添加失败!")
return False
# 校验成绩范围
if not (0 <= score <= 100):
print(f"错误:成绩{score}超出范围(0-100),添加失败!")
return False
# 校验年龄是否为正整数
if not (isinstance(age, int) and age > 0):
print(f"错误:年龄{age}无效(需为正整数),添加失败!")
return False
# 添加学生信息
students[student_id] = {
"name": name,
"age": age,
"score": score
}
print(f"成功:学号{student_id}({name})添加完成!")
return True
# ------------------------------
# 功能函数:查询学生
# ------------------------------
def query_student(students, query_type, keyword):
"""
查询学生信息
参数:
students (dict): 存储学生信息的字典
query_type (str): 查询类型("id":按学号,"name":按姓名)
keyword (str): 查询关键词(学号或姓名)
返回:
list: 匹配的学生列表(每个元素是(学号,信息字典)的元组)
"""
results = []
if query_type == "id":
# 按学号查询(精确匹配)
if keyword in students:
results.append((keyword, students[keyword]))
elif query_type == "name":
# 按姓名查询(模糊匹配,不区分大小写)
for student_id, info in students.items():
if keyword.lower() in info["name"].lower():
results.append((student_id, info))
else:
print("错误:查询类型无效(仅支持'id'或'name')!")
return []
# 显示查询结果
if not results:
print(f"查询结果:未找到匹配“{keyword}”的学生!")
else:
print(f"\n查询结果(共{len(results)}条):")
print("-" * 50)
print(f"{'学号':<10} {'姓名':<10} {'年龄':<5} {'成绩':<5}")
print("-" * 50)
for student_id, info in results:
print(f"{student_id:<10} {info['name']:<10} {info['age']:<5} {info['score']:<5}")
print("-" * 50)
return results
# ------------------------------
# 功能函数:修改成绩
# ------------------------------
def modify_score(students, student_id, new_score):
"""
修改学生成绩
参数:
students (dict): 存储学生信息的字典
student_id (str): 学号
new_score (int): 新成绩(0-100)
返回:
bool: True(修改成功),False(修改失败)
"""
# 校验学号是否存在
if student_id not in students:
print(f"错误:学号{student_id}不存在,修改失败!")
return False
# 校验新成绩范围
if not (0 <= new_score <= 100):
print(f"错误:成绩{new_score}超出范围(0-100),修改失败!")
return False
# 保存原成绩,用于打印日志
old_score = students[student_id]["score"]
students[student_id]["score"] = new_score
print(f"成功:学号{student_id}({students[student_id]['name']})成绩修改完成!")
print(f"原成绩:{old_score} → 新成绩:{new_score}")
return True
# ------------------------------
# 功能函数:删除学生
# ------------------------------
def delete_student(students, student_id):
"""
删除学生信息
参数:
students (dict): 存储学生信息的字典
student_id (str): 学号
返回:
bool: True(删除成功),False(删除失败)
"""
# 校验学号是否存在
if student_id not in students:
print(f"错误:学号{student_id}不存在,删除失败!")
return False
# 确认删除(避免误操作)
name = students[student_id]["name"]
confirm = input(f"确认删除学号{student_id}({name})吗?(y/n):")
if confirm.lower() != "y":
print("取消删除操作!")
return False
# 删除学生信息
del students[student_id]
print(f"成功:学号{student_id}({name})删除完成!")
return True
# ------------------------------
# 功能函数:统计分析
# ------------------------------
@time_decorator # 用装饰器统计执行时间
def analyze_data(students):
"""
统计学生成绩数据(平均分、最高分、最低分、及格率)
参数:
students (dict): 存储学生信息的字典
返回:
dict: 统计结果字典(包含avg、max、min、pass_rate)
"""
if not students:
print("错误:无学生数据可统计!")
return {}
# 提取所有学生的成绩
scores = [info["score"] for info in students.values()]
# 计算统计指标
avg_score = sum(scores) / len(scores)
max_score = max(scores)
min_score = min(scores)
pass_count = len([s for s in scores if s >= 60]) # 及格人数(≥60分)
pass_rate = (pass_count / len(scores)) * 100
# 显示统计结果
print("\n" + "=" * 30)
print("学生成绩统计分析")
print("=" * 30)
print(f"总人数:{len(students)}人")
print(f"平均分:{avg_score:.2f}分")
print(f"最高分:{max_score}分")
print(f"最低分:{min_score}分")
print(f"及格人数:{pass_count}人(≥60分)")
print(f"及格率:{pass_rate:.2f}%")
print("=" * 30)
return {
"avg": avg_score,
"max": max_score,
"min": min_score,
"pass_rate": pass_rate
}
# ------------------------------
# 辅助函数:显示主菜单
# ------------------------------
def show_menu():
print("\n" + "=" * 30)
print(" 学生管理系统")
print("=" * 30)
print("1. 添加学生信息")
print("2. 查询学生信息")
print("3. 修改学生成绩")
print("4. 删除学生信息")
print("5. 成绩统计分析")
print("6. 退出系统")
print("=" * 30)
# ------------------------------
# 主函数:控制系统流程
# ------------------------------
def main():
# 初始化学生数据字典(key:学号,value:信息字典)
students = {}
while True:
show_menu()
# 获取用户选择
choice = input("请输入操作编号(1-6):")
# 1. 添加学生
if choice == "1":
print("\n--- 添加学生信息 ---")
student_id = input("请输入学号:")
name = input("请输入姓名:")
# 处理年龄输入(确保为整数)
try:
age = int(input("请输入年龄:"))
except ValueError:
print("错误:年龄需为整数!")
continue
# 处理成绩输入(确保为整数)
try:
score = int(input("请输入成绩:"))
except ValueError:
print("错误:成绩需为整数!")
continue
# 调用添加函数
add_student(students, student_id, name, age, score)
# 2. 查询学生
elif choice == "2":
print("\n--- 查询学生信息 ---")
query_type = input("请选择查询类型(id:按学号,name:按姓名):")
keyword = input(f"请输入要查询的{query_type}:")
# 调用查询函数
query_student(students, query_type, keyword)
# 3. 修改成绩
elif choice == "3":
print("\n--- 修改学生成绩 ---")
student_id = input("请输入要修改的学生学号:")
# 处理新成绩输入
try:
new_score = int(input("请输入新成绩:"))
except ValueError:
print("错误:成绩需为整数!")
continue
# 调用修改函数
modify_score(students, student_id, new_score)
# 4. 删除学生
elif choice == "4":
print("\n--- 删除学生信息 ---")
student_id = input("请输入要删除的学生学号:")
# 调用删除函数
delete_student(students, student_id)
# 5. 统计分析
elif choice == "5":
print("\n--- 成绩统计分析 ---")
# 调用统计函数(带计时装饰器)
analyze_data(students)
# 6. 退出系统
elif choice == "6":
confirm = input("确认退出系统吗?(y/n):")
if confirm.lower() == "y":
print("感谢使用学生管理系统,再见!")
break
# 无效选择
else:
print("错误:请输入1-6之间的有效编号!")
# ------------------------------
# 程序入口:执行主函数
# ------------------------------
if __name__ == "__main__":
main()
6.3 系统功能测试
运行代码后,可通过命令行菜单测试各功能:
测试 1:添加学生
==============================
学生管理系统
==============================
1. 添加学生信息
2. 查询学生信息
3. 修改学生成绩
4. 删除学生信息
5. 成绩统计分析
6. 退出系统
==============================
请输入操作编号(1-6):1
--- 添加学生信息 ---
请输入学号:2023001
请输入姓名:张三
请输入年龄:20
请输入成绩:85
成功:学号2023001(张三)添加完成!
测试 2:查询学生
请输入操作编号(1-6):2
--- 查询学生信息 ---
请选择查询类型(id:按学号,name:按姓名):name
请输入要查询的name:张
查询结果(共1条):
--------------------------------------------------
学号 姓名 年龄 成绩
--------------------------------------------------
2023001 张三 20 85
--------------------------------------------------
测试 3:统计分析
请输入操作编号(1-6):5
--- 成绩统计分析 ---
==============================
学生成绩统计分析
==============================
总人数:1人
平均分:85.00分
最高分:85分
最低分:85分
及格人数:1人(≥60分)
及格率:100.00%
==============================
[计时] 函数analyze_data执行时间:0.000012秒
七、总结与扩展:掌握函数,提升 Python 编程能力
7.1 核心知识点回顾
本文从基础到进阶,系统讲解了 Python 函数的核心内容:
- 基础语法:def定义、参数列表、return返回值、文档字符串;
- 参数类型:位置参数、关键字参数、默认参数、*args/**kwargs,以及参数组合顺序;
- 作用域规则:LEGB 查找顺序,global/nonlocal关键字的使用;
- 高阶特性:函数作为参数 / 返回值、lambda匿名函数、map/filter/reduce内置高阶函数;
- 装饰器:基础装饰器、带参数装饰器、functools.wraps保留元信息,以及常见应用场景;
- 实战应用:通过学生管理系统,整合所有知识点,实现模块化编程。
7.2 进阶扩展方向
掌握基础后,可进一步探索函数的高级应用:
- 函数式编程:深入学习纯函数、不可变数据、functools模块(如lru_cache缓存、partial偏函数);
- 异步函数:Python 3.5 + 引入的async def异步函数,用于处理 IO 密集型任务(如网络请求、文件读写);
- 函数注解与类型提示:Python 3.0 + 支持的函数注解(如def add(a: int, b: int) -> int),结合mypy工具实现静态类型检查;
- 自定义装饰器库:学习成熟的装饰器库(如decorator),简化复杂装饰器的编写;
- 闭包与工厂函数:深入理解闭包的原理,利用工厂函数批量生成相似功能的函数。
7.3 编程建议
- 模块化设计:将复杂逻辑拆分为多个小函数,每个函数仅负责单一功能(“单一职责原则”);
- 可读性优先:函数名、参数名需见名知意,文档字符串清晰说明功能、参数、返回值;
- 避免过度封装:简单逻辑无需强行封装为函数,避免 “为了封装而封装”;
- 谨慎使用全局变量:优先通过参数传递数据,减少函数对外部环境的依赖;
- 测试驱动开发:为每个函数编写单元测试(如unittest/pytest),确保功能正确性。
函数是 Python 编程的 “基本功”,也是进阶高级开发(如框架开发、数据分析、自动化测试)的基础。只有熟练掌握函数的各种特性,才能写出高效、优雅、可维护的 Python 代码。

1200

被折叠的 条评论
为什么被折叠?



