Python 函数深度解析:从基础到进阶的全方位指南

在 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)

核心要素解析:
  1. 函数名:遵循 Python 命名规范(小写字母 + 下划线,如user_info),需见名知意;
  1. 参数列表:函数接收的 “输入数据”,可无参数(如def hello():),也可多个参数;
  1. 文档字符串:用三引号包裹的说明文本,通过函数名.__doc__或help(函数名)查看,提升代码可读性;
  1. 函数体:缩进的代码块(Python 强制缩进,通常 4 个空格),实现具体逻辑;
  1. 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 会报错。正确顺序为:

  1. 位置参数(如a, b)
  1. 默认参数(如c=0)
  1. *args(可变位置参数)
  1. 关键字 - only 参数(Python 3 新增,需在*args后,用*分隔或直接在*args后定义)
  1. **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 规则查找变量,即从 “近” 到 “远” 依次查找,找到即停止:

  1. L(Local):局部作用域:函数内部定义的变量,仅在函数内部可访问;
  1. E(Enclosing):嵌套作用域:外层嵌套函数的作用域(如函数内定义的子函数);
  1. G(Global):全局作用域:模块(.py 文件)层面定义的变量,整个模块可访问;
  1. 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. 接收一个或多个函数作为参数;
  1. 返回一个函数作为结果。
示例 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”

解析

  1. @log_decorator是装饰器的语法糖,等价于add = log_decorator(add);
  1. log_decorator(add)接收add函数作为参数,返回wrapper函数;
  1. 后续调用add(3,4),实际调用的是wrapper(3,4);
  1. 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

解析

  1. @log_decorator(prefix="DEBUG")等价于multiply = log_decorator(prefix="DEBUG")(multiply);
  1. 第一层log_decorator(prefix="DEBUG")返回decorator函数;
  1. 第二层decorator(multiply)接收multiply函数,返回wrapper函数;
  1. 最终调用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 系统需求与设计

核心需求:
  1. 数据存储:用字典存储学生信息(学号:{姓名、年龄、成绩});
  1. 功能模块:每个功能对应一个函数,主函数控制流程;
  1. 用户交互:命令行菜单,支持循环操作;
  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 函数的核心内容:

  1. 基础语法:def定义、参数列表、return返回值、文档字符串;
  1. 参数类型:位置参数、关键字参数、默认参数、*args/**kwargs,以及参数组合顺序;
  1. 作用域规则:LEGB 查找顺序,global/nonlocal关键字的使用;
  1. 高阶特性:函数作为参数 / 返回值、lambda匿名函数、map/filter/reduce内置高阶函数;
  1. 装饰器:基础装饰器、带参数装饰器、functools.wraps保留元信息,以及常见应用场景;
  1. 实战应用:通过学生管理系统,整合所有知识点,实现模块化编程。

7.2 进阶扩展方向

掌握基础后,可进一步探索函数的高级应用:

  1. 函数式编程:深入学习纯函数、不可变数据、functools模块(如lru_cache缓存、partial偏函数);
  1. 异步函数:Python 3.5 + 引入的async def异步函数,用于处理 IO 密集型任务(如网络请求、文件读写);
  1. 函数注解与类型提示:Python 3.0 + 支持的函数注解(如def add(a: int, b: int) -> int),结合mypy工具实现静态类型检查;
  1. 自定义装饰器库:学习成熟的装饰器库(如decorator),简化复杂装饰器的编写;
  1. 闭包与工厂函数:深入理解闭包的原理,利用工厂函数批量生成相似功能的函数。

7.3 编程建议

  1. 模块化设计:将复杂逻辑拆分为多个小函数,每个函数仅负责单一功能(“单一职责原则”);
  1. 可读性优先:函数名、参数名需见名知意,文档字符串清晰说明功能、参数、返回值;
  1. 避免过度封装:简单逻辑无需强行封装为函数,避免 “为了封装而封装”;
  1. 谨慎使用全局变量:优先通过参数传递数据,减少函数对外部环境的依赖;
  1. 测试驱动开发:为每个函数编写单元测试(如unittest/pytest),确保功能正确性。

函数是 Python 编程的 “基本功”,也是进阶高级开发(如框架开发、数据分析、自动化测试)的基础。只有熟练掌握函数的各种特性,才能写出高效、优雅、可维护的 Python 代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小辉!

技术路有你,打赏助我分享

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值