本期目标
一、为什么需要函数?
回顾前几期,我们写了不少代码:统计成绩、词频统计……这些代码虽然能运行,但存在一个共同问题:如果你想重复使用某段逻辑,就必须复制粘贴。这不仅让代码变得臃肿,还容易出错——如果逻辑需要修改,你得在所有复制的地方一一修改。
举个例子,假设你需要多次计算圆的面积:
# 第一次计算r1 = 5area1 = 3.14159 * r1 ** 2print(area1)# 第二次计算(又写一遍)r2 = 7area2 = 3.14159 * r2 ** 2print(area2)# 第三次…… 如果要求改 π 的精度,所有地方都要改
这样的代码很“笨拙”。但如果用函数,你就可以把这段逻辑“封装”起来,给它起个名字,需要的时候“调用”一下即可:
def calc_circle_area(radius): pi = 3.14159 return pi * radius ** 2# 需要时直接调用print(calc_circle_area(5)) # 78.53975print(calc_circle_area(7)) # 153.93791
函数的核心价值在于:
- 代码复用: 一次定义,多次调用
- 模块化: 把复杂问题拆分成小模块,每个函数只做一件事
- 易于维护: 修改函数内部逻辑,所有调用处自动生效
- 可读性强: 好的函数名就像注释,一看就知道功能
在AI领域,函数更是无处不在:
- 数据预处理函数:
load_data()、clean_data()、normalize() - 模型构建函数:
build_model()、train_model()、evaluate() - 工具函数:
plot_accuracy()、save_checkpoint()
二、函数的定义与调用
1. 基本语法
Python 使用 def 关键字定义函数,基本结构如下:
def 函数名(参数1, 参数2, ...): """文档字符串(可选,描述函数功能)""" 函数体代码 return 返回值 # 可选
示例:一个最简单的函数
# 定义函数def say_hello(): """打印问候语""" print("Hello, AI World!")# 调用函数say_hello() # 输出:Hello, AI World!
⚠️ 注意:定义函数只是“声明”了它,并不会执行函数体内的代码。只有当你“调用”函数(写上函数名加括号)时,代码才会运行。
2. 带参数的函数
函数可以接收输入数据(参数),让功能更灵活:
def greet(name): """向指定的人打招呼""" print(f"你好,{name}!")greet("张三") # 你好,张三!greet("李四") # 你好,李四!
3. 带返回值的函数(return)
return 语句用于将函数的结果“返回”给调用者。一旦执行 return,函数立即结束。
def add(a, b): """返回两个数的和""" return a + bresult = add(3, 5) # result 接收返回值print(result) # 8
没有 return 的函数默认返回 None:
def do_nothing(): pass # pass 是空语句,占位用val = do_nothing()print(val) # None
4. 参数与返回值的灵活组合
- 无参数无返回值: 执行某些操作(如打印菜单)
- 有参数无返回值: 处理数据但不返回(如打印统计信息)
- 无参数有返回值: 获取某些数据(如获取当前时间)
- 有参数有返回值: 最常用,计算并返回结果
三、函数的参数详解
1. 位置参数(Positional Arguments)
调用函数时,传入的参数按位置依次赋值给形参:
def introduce(name, age, city): print(f"{name},{age}岁,来自{city}")introduce("张三", 20, "北京") # 张三,20岁,来自北京introduce("李四", 25, "上海") # 李四,25岁,来自上海
顺序必须一一对应,否则会出错或逻辑混乱。
2. 关键字参数(Keyword Arguments)
调用时可以通过“参数名=值”的方式传递,顺序可以打乱:
introduce(age=20, city="北京", name="张三") # 正确,顺序无关
关键字参数提高了代码可读性,尤其在参数较多时非常有用。
3. 默认参数(Default Arguments)
定义函数时可以为参数指定默认值,调用时如果不传该参数,则使用默认值:
def greet(name, greeting="你好"): print(f"{greeting},{name}!")greet("张三") # 你好,张三!greet("李四", "Hello") # Hello,李四!
⚠️ 重要规则:默认参数必须放在所有没有默认值的参数之后,否则会报错。
# ❌ 错误def greet(greeting="你好", name):<br> ...# ✅ 正确def greet(name, greeting="你好"):<br> ...、
💡 小贴士:默认参数在AI中常用于配置函数,比如 train_model(data, lr=0.001, epochs=100),大部分情况下使用默认值即可,但需要调参时可以灵活覆盖。
4. 可变参数(*args 和 **kwargs)——了解即可,下期扩展
Python 还支持接收任意数量的位置参数(*args)和关键字参数(**kwargs),我们在下期会详细讲解。
四、作用域(Scope)——变量的可见范围
1. 局部变量 vs 全局变量
- 局部变量: 在函数内部定义的变量,只能在该函数内使用
- 全局变量: 在函数外部定义的变量,可以在整个程序中访问(但函数内部如需修改,需要
global 声明)
# 全局变量x = 10def test(): y = 5 # 局部变量 print(x) # 可以访问全局变量 x(只读) print(y)test() # 10, 5print(x) # 10(全局变量仍然存在)# print(y) # ❌ NameError:y 在函数外部不可见
在函数内部修改全局变量(不推荐,容易造成混乱):
x = 10def change_global(): global x # 声明要修改全局变量 x x = 20change_global()print(x) # 20
⚠️ 最佳实践:尽量避免在函数内部修改全局变量。如果需要修改,可以通过返回值的方式实现,保持函数“纯粹性”。
2. 变量查找规则(LEGB)
Python 查找变量的顺序是:Local(局部)→ Enclosing(外层函数)→ Global(全局)→ Built-in(内置)。这个概念在嵌套函数中会用到,目前只需记住:函数内部优先使用局部变量。
五、实战项目:常用数学工具函数库
现在,我们利用函数把之前分散的数学计算整理成一个工具库,方便以后复用。
# ============================================# 数学工具函数库(math_utils.py)# 包含:阶乘、斐波那契数列、质数判断、最大公约数、最小公倍数# ============================================def factorial(n): """ 计算 n 的阶乘(n!) 例如:5! = 5*4*3*2*1 = 120 """ if n < 0: return None # 负数没有阶乘 result = 1 for i in range(2, n + 1): result *= i return resultdef fibonacci(n): """ 生成斐波那契数列的前 n 项(列表形式) 斐波那契:第1项=0,第2项=1,之后每一项是前两项之和 """ if n <= 0: return [] elif n == 1: return [0] elif n == 2: return [0, 1] else: seq = [0, 1] for i in range(2, n): seq.append(seq[-1] + seq[-2]) return seqdef is_prime(num): """ 判断一个正整数是否为质数(只能被1和自身整除) """ if num < 2: return False # 只需检查到 sqrt(num) 即可 for i in range(2, int(num ** 0.5) + 1): if num % i == 0: return False return Truedef gcd(a, b): """ 计算 a 和 b 的最大公约数(欧几里得算法) """ while b != 0: a, b = b, a % b return adef lcm(a, b): """ 计算 a 和 b 的最小公倍数 公式:a * b / gcd(a, b) """ return a * b // gcd(a, b)# ========== 测试函数 ==========if __name__ == "__main__": print("===== 数学工具库测试 =====") # 阶乘 print(f"5! = {factorial(5)}") # 120 print(f"0! = {factorial(0)}") # 1 # 斐波那契 print(f"前10项斐波那契:{fibonacci(10)}") # 质数判断 print(f"17是质数吗?{is_prime(17)}") # True print(f"20是质数吗?{is_prime(20)}") # False # 最大公约数 print(f"48和36的最大公约数:{gcd(48, 36)}") # 12 # 最小公倍数 print(f"12和18的最小公倍数:{lcm(12, 18)}") # 36
运行效果:
===== 数学工具库测试 =====5! = 1200! = 1前10项斐波那契:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]17是质数吗?True20是质数吗?False48和36的最大公约数:1212和18的最小公倍数:36
实战扩展:用户交互式数学工具
我们可以再写一个交互式程序,让用户选择使用哪个工具:
def show_math_menu(): """显示数学工具菜单""" print("\n" + "=" * 40) print(" 🔢 数学工具库") print("=" * 40) print(" 1. 计算阶乘") print(" 2. 生成斐波那契数列") print(" 3. 判断质数") print(" 4. 计算最大公约数") print(" 5. 计算最小公倍数") print(" 6. 退出") print("=" * 40)def math_tool(): """主交互程序""" while True: show_math_menu() choice = input("请选择功能 (1-6):") if choice == "1": n = int(input("请输入一个非负整数:")) result = factorial(n) print(f"{n}! = {result}") elif choice == "2": n = int(input("请输入项数:")) result = fibonacci(n) print(f"前{n}项:{result}") elif choice == "3": n = int(input("请输入一个正整数:")) result = is_prime(n) print(f"{n} {'是' if result else '不是'}质数") elif choice == "4": a = int(input("请输入第一个数:")) b = int(input("请输入第二个数:")) result = gcd(a, b) print(f"最大公约数:{result}") elif choice == "5": a = int(input("请输入第一个数:")) b = int(input("请输入第二个数:")) result = lcm(a, b) print(f"最小公倍数:{result}") elif choice == "6": print("👋 感谢使用,再见!") break else: print("❌ 无效选择,请重新输入!")# 启动工具if __name__ == "__main__": math_tool()
效果展示:
效果展示六、动手练习
练习1:定义简单函数
编写一个函数 is_even(num),接收一个整数,如果它是偶数返回 True,否则返回 False。
👆 点击查看参考答案def is_even(num): return num % 2 == 0print(is_even(4)) # Trueprint(is_even(7)) # False
练习2:默认参数的应用
编写一个函数 create_user(name, age=18, city="未知"),打印用户信息。调用时分别传入不同参数验证。
👆 点击查看参考答案def create_user(name, age=18, city="未知"): print(f"用户:{name},年龄:{age},城市:{city}")create_user("张三") # 用户:张三,年龄:18,城市:未知create_user("李四", 25) # 用户:李四,年龄:25,城市:未知create_user("王五", 30, "北京") # 用户:王五,年龄:30,城市:北京
练习3:作用域理解
分析以下代码,写出输出结果,并解释为什么:
x = 100def outer(): x = 200 def inner(): print(x) inner()outer()print(x)
👆 点击查看参考答案# 输出:# 200# 100# 解释:outer 内部定义了局部变量 x=200,inner 打印的 x 是 outer 的局部变量(闭包),所以是 200。outer 外部的全局 x 未受影响,仍为 100。
本期小结
本期我们迈入了“模块化编程”的大门:
| |
|---|
| 函数定义 | def 函数名(参数): |
| 参数传递 | 位置参数(按顺序)、关键字参数(指定名)、默认参数(提供默认值) |
| 返回值 | return |
| 作用域 | 局部变量(函数内)和全局变量(函数外),尽量少用 global |
| 模块化思想 | |
下期预告:我们将深入学习函数的高级特性——匿名函数(lambda)、装饰器、以及可变参数 *args 和 `kwargs`**,让你的函数更灵活、更强大!
📌 本期闲谈:
网上很多人说现在项目都ai写了,全都vibe coding了,谁还学习古法编程。我也迷茫了,是不是不应该浪费时间学习,直接all in ai。周末跟朋友聊了聊,他说,ai确实非常方便,想实现一个功能,用ai比自己写快的多,而且ai发展的特别快,刚开始的时候第一款ai软件还是cursor那时他就开始用了,当时的ai能力非常差只能实现简单的代码补全。现在有个想法或具体的功能,ai实现的很快也很好。但ai也有风险,今年六月左右Claude Code爆出隐蔽的检测代码,用日期格式识别中国用户,更早之前ai中转站投毒,生成带恶意脚本的批处理文件。编程学习基础语法肯定没错,你总不能看不懂ai输出的是什么东西就直接运行吧