第3章 3.1 def 定义函数
🎯 开场:为什么你需要一个"函数"?
上一章我们做了两个小项目:猜数字游戏和九九乘法表。不知道你有没有这种感觉——写到最后,代码越来越长,有些地方反复出现(比如猜数字里的"猜错了提示",九九乘法表里的"换行逻辑")。
举个例子你就懂了。
你写了一个计算工资的程序,里面有5个地方都要"判断是否超过个税起征点"。结果产品经理说"起征点改成6000了",你得翻遍整个文件,一个一个改。
痛点来了:
- 重复代码太多:同样的逻辑复制粘贴了 N 遍,改一处要改 N 处
- 代码读不懂:一个函数写 200 行,里面绕来绕去,自己都看不懂
- 没法复用:写过的代码换个项目想用,还得复制粘贴一堆
学完这一章,上面的问题全部消失。你会掌握一个编程界的"神器"——函数。
🧱 基础:什么是函数?
3.1.1 生活类比:函数就像"外卖小哥"
想象你点外卖。你不需要知道外卖小哥怎么骑车、怎么规划路线、怎么爬楼梯——你只做一件事:下单,然后等外卖送到手里。
函数就是这样一段"可以被反复调用"的代码。你定义一次,可以调用无数次。
定义函数 = 制定一个标准操作流程(比如"做一杯奶茶")
调用函数 = 下单,让系统执行这个流程
3.1.2 第一个函数:say_hello
废话少说,先看代码:
def say_hello():
"""打招呼函数"""
print("你好呀,我是小张!")
# 调用函数
say_hello()
输出:
你好呀,我是小张!
解释:
- def 是 Python 里定义函数的关键字(英文"define"的缩写)
- say_hello 是函数名,你爱叫什么叫,但最好见名知意
- () 里是参数(后面讲),这里先空着
- """...""" 是函数的说明文档,叫 docstring,写清楚这个函数干嘛的
3.1.3 带参数的函数:让函数更灵活
刚才的 say_hello 太死板了,每次都只能说"小张"。加上参数就不一样了:
def say_hello(name):
"""向指定的人打招呼"""
print(f"你好呀,我是{name}!")
say_hello("小明")
say_hello("小红")
say_hello("老王")
输出:
你好呀,我是小明!
你好呀,我是小红!
你好呀,我是老王!
解释:
- name 是个参数,像是一个"占位符"
- 调用时传进去的值叫实参(实际参数)
- f"..." 是格式化字符串,{name} 会显示变量的值
💡 类比:参数就像点外卖时的"备注"——你备注"少糖",外卖小哥就知道做少糖的。
3.1.4 有返回值的函数:函数不只打印,还能"交货"
刚才的函数都是"打印出来",但有时候我们需要函数"返回一个结果",而不是直接打印。
def add(a, b):
"""计算两个数的和"""
result = a + b
return result
# 把返回值存到变量里
total = add(3, 5)
print(f"3 + 5 = {total}")
输出:
3 + 5 = 8
解释:
- return result 就是函数"交货"的方式
- 调用 add(3, 5) 会返回一个值 8,存到 total 里
- 没有 return 的函数,返回 None

3.1.5 默认参数:让参数有"默认值"
有时候我们希望某些参数是可选的,不传就使用默认值:
def greet(name, greeting="你好"):
"""带默认问候语的打招呼"""
print(f"{greeting},{name}!")
greet("小明") # 使用默认问候语
greet("小红", "早上好") # 自定义问候语
输出:
你好,小明!
早上好,小红!
解释:
- greeting="你好" 这个就是默认参数
- 调用时不传 greeting,就用默认值
- 传了,就用你传的值
⚠️ 注意:默认参数必须放在普通参数后面!
def greet(name, greeting="你好")可以,但def greet(greeting="你好", name)不行。
3.1.6 多返回值:一次返回多个值
函数能不能一次返回多个值?可以!Python 会把它打包成一个元组:
def get_stats(numbers):
"""计算一组数的和与平均值"""
total = sum(numbers)
average = total / len(numbers)
return total, average
result = get_stats([10, 20, 30, 40, 50])
print(f"总和:{result[0]},平均值:{result[1]}")
# 也可以用解包,更优雅
total, average = get_stats([10, 20, 30, 40, 50])
print(f"总和:{total},平均值:{average}")
输出:
总和:150,平均值:30.0
总和:150,平均值:30.0
3.1.7 参数传递:值传递 vs 引用传递(生活类比)
这是新手最容易懵的地方。记住两个原则:
| 类型 | 能不能改原值 | 例子 |
|---|---|---|
| 不可变类型(int, str, tuple) | 不能改 | 数字、字符串 |
| 可变类型(list, dict) | 可能改 | 列表、字典 |
def try_modify(x, nums):
"""尝试修改参数"""
x = 100 # 这改不了外面的 x
nums.append(99) # 这会改外面的 nums
a = 5
b = [1, 2, 3]
try_modify(a, b)
print(f"a = {a}") # a 还是 5,没变!
print(f"b = {b}") # b 变成了 [1, 2, 3, 99],被改了!
输出:
a = 5
b = [1, 2, 3, 99]
💡 类比:想象你把一本纸质书复印了一份给别人。他可以在复印件上涂涂画画,但你的原书不受影响(值传递)。但如果你把书的借书证借给他,他用借书证去图书馆改了你的借阅记录,你的书就真的被改了(引用传递)。
3.1.8 函数的文档:docstring
写清楚你的函数是干嘛的,方便自己和别人以后阅读:
def calculate_area(width, height):
"""
计算矩形面积
参数:
width (float): 宽度
height (float): 高度
返回:
float: 矩形面积
"""
return width * height
# 查看文档
print(calculate_area.__doc__)
help(calculate_area)

🔥 实战:三个小项目
📦 项目 1:温度转换器(5分钟)
场景:你有个外国朋友,他习惯用华氏度,你习惯用摄氏度,需要互相转换。
def celsius_to_fahrenheit(c):
"""摄氏转华氏"""
return c * 9/5 + 32
def fahrenheit_to_celsius(f):
"""华氏转摄氏"""
return (f - 32) * 5/9
# 演示
print("=== 温度转换器 ===")
print(f"0°C = {celsius_to_fahrenheit(0):.1f}°F")
print(f"100°C = {celsius_to_fahrenheit(100):.1f}°F")
print(f"32°F = {fahrenheit_to_celsius(32):.1f}°C")
print(f"212°F = {fahrenheit_to_celsius(212):.1f}°C")
预期输出:
=== 温度转换器 ===
0°C = 32.0°F
100°C = 212.0°F
32°F = 0.0°C
212°F = 212.0°C
一句话解释:定义了2个函数,分别处理两个方向的转换,调用时直接传值就行。
📦 项目 2:个人账单统计工具(15分钟)
场景:你记了一周的消费记录,想知道总花费、平均日花费、最高的单笔支出。
def calculate_total(amounts):
"""计算总花费"""
return sum(amounts)
def calculate_average(amounts):
"""计算平均日花费"""
return sum(amounts) / len(amounts)
def find_max_expense(amounts, descriptions):
"""找出最高的一笔消费"""
max_idx = amounts.index(max(amounts))
return descriptions[max_idx], amounts[max_idx]
def find_min_expense(amounts, descriptions):
"""找出最低的一笔消费"""
min_idx = amounts.index(min(amounts))
return descriptions[min_idx], amounts[min_idx]
# 模拟一周的消费记录
descriptions = ["早餐", "午餐", "地铁", "咖啡", "晚餐", "网购"]
amounts = [15, 25, 5, 30, 80, 200]
# 统计
print("=== 本周消费账单 ===")
print(f"消费笔数:{len(amounts)} 笔")
print(f"总花费:{calculate_total(amounts)} 元")
print(f"平均日花费:{calculate_average(amounts):.1f} 元")
max_desc, max_amount = find_max_expense(amounts, descriptions)
print(f"最高消费:{max_desc},{max_amount} 元")
min_desc, min_amount = find_min_expense(amounts, descriptions)
print(f"最低消费:{min_desc},{min_amount} 元")
预期输出:
=== 本周消费账单 ===
消费笔数:6 笔
总花费:355 元
平均日花费:59.2 元
最高消费:网购,200 元
最低消费:地铁,5 元
一句话解释:把统计逻辑拆成4个独立的函数,每个函数只做一件事,清晰又好维护。
📦 项目 3:待办事项管理器(15分钟)
场景:你需要一个命令行版的待办清单,能添加、查看、标记完成、删除事项。
def show_menu():
"""显示菜单"""
print("\n=== 待办事项管理器 ===")
print("1. 查看所有事项")
print("2. 添加新事项")
print("3. 标记事项完成")
print("4. 删除事项")
print("5. 退出")
def show_todos(todos):
"""显示所有待办事项"""
if not todos:
print("📝 暂无待办事项")
return
print("\n📋 你的待办清单:")
for i, todo in enumerate(todos, 1):
status = "✅" if todo["done"] else "⬜"
print(f" {i}. {status} {todo['content']}")
def add_todo(todos, content):
"""添加新事项"""
todos.append({"content": content, "done": False})
print(f"✅ 已添加:{content}")
def complete_todo(todos, index):
"""标记事项完成"""
if 0 < index <= len(todos):
todos[index-1]["done"] = True
print(f"✅ 已完成:{todos[index-1]['content']}")
else:
print("❌ 无效的序号")
def delete_todo(todos, index):
"""删除事项"""
if 0 < index <= len(todos):
removed = todos.pop(index-1)
print(f"🗑️ 已删除:{removed['content']}")
else:
print("❌ 无效的序号")
# 主程序
todos = [
{"content": "完成 Python 函数章节学习", "done": False},
{"content": "练习本章节的5道题", "done": False},
]
while True:
show_menu()
choice = input("\n请选择操作(1-5):")
if choice == "1":
show_todos(todos)
elif choice == "2":
content = input("请输入待办内容:")
add_todo(todos, content)
elif choice == "3":
show_todos(todos)
index = int(input("请输入要完成的事项序号:"))
complete_todo(todos, index)
elif choice == "4":
show_todos(todos)
index = int(input("请输入要删除的事项序号:"))
delete_todo(todos, index)
elif choice == "5":
print("👋 再见!")
break
else:
print("❌ 无效选择,请重新输入")
预期输出(交互示例):
=== 待办事项管理器 ===
1. 查看所有事项
2. 添加新事项
3. 标记事项完成
4. 删除事项
5. 退出
请选择操作(1-5):1
📋 你的待办清单:
1. ⬜ 完成 Python 函数章节学习
2. ⬜ 练习本章节的5道题
一句话解释:把每个操作都写成独立函数,主循环只负责调用,代码清晰得像写中文一样。
💪 进阶:常见坑 + 技巧
❌ 坑 1:忘记加冒号
# ❌ 错误
def say_hello(name)
print(f"你好,{name}")
# ✅ 正确
def say_hello(name):
print(f"你好,{name}")
解释:def 那一行末尾必须加 :,这是 Python 的语法规则。
❌ 坑 2:默认参数写成可变对象
# ❌ 错误 - 踩坑预警
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("苹果")) # ['苹果']
print(add_item("香蕉")) # ['苹果', '香蕉'] ← 预期是只有'香蕉'!
# ✅ 正确
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item("苹果")) # ['苹果']
print(add_item("香蕉")) # ['香蕉']
解释:默认参数 [] 在函数定义时创建一次,可变类型会被记住。用 None 代替,每次调用都会创建新列表。
❌ 坑 3:变量名和函数名冲突
# ❌ 容易混淆
def sum(a, b):
return a + b
sum = 10 # 这行让 sum 不再是函数了!
print(sum(1, 2)) # TypeError: 'int' object is not callable
# ✅ 正确:换个变量名
def sum_numbers(a, b):
return a + b
total = 10 # 用 total 存结果
print(sum_numbers(1, 2)) # 3
❌ 坑 4:函数调用顺序错误
# ❌ 错误 - 调用在定义之前
greet("小明")
def greet(name):
print(f"你好,{name}")
# ✅ 正确 - 先定义后调用
def greet(name):
print(f"你好,{name}")
greet("小明")
❌ 坑 5:return 和 print 混淆
# ❌ 错误 - 想返回结果却打印了
def multiply(a, b):
print(a * b) # 这是打印,不是返回
result = multiply(3, 4)
print(result) # None,因为没有 return
# ✅ 正确
def multiply(a, b):
return a * b # 返回结果
result = multiply(3, 4)
print(result) # 12
🔧 调试技巧:用 print 大法
def buggy_function(x):
"""一个可能有 bug 的函数"""
print(f"🔍 DEBUG: x = {x}") # 打印中间值
result = x * 2 + 1
print(f"🔍 DEBUG: result = {result}")
return result
buggy_function(5)
输出:
🔍 DEBUG: x = 5
🔍 DEBUG: result = 11
⚡ 性能小贴士:避免重复计算
# ❌ 低效:每次调用都重新计算
def calculate_circle_area(r):
return 3.14159 * r ** 2
# ✅ 高效:用局部变量缓存常量
def calculate_circle_area(r):
PI = 3.14159 # 常量放函数内,避免全局查找
return PI * r ** 2
# ✅ 更高效:用 math 模块的 PI
import math
def calculate_circle_area(r):
return math.pi * r ** 2
✏️ 练习题
练习 1(2 分钟):基础题 - 改个问候语
- 输入:调用 greet("老张")
- 预期输出:晚上好,老张!
- 提示:看看项目 1 的 greet 函数,默认参数是什么?
练习 2(2 分钟):基础题 - 加个判断
- 输入:给定数字 87,判断是否及格(60分)
- 预期输出:87 分,及格! 或 45 分,不及格!
- 提示:用一个 check_pass(score) 函数,里面加 if 判断
练习 3(3 分钟):进阶题 - 算平均分
- 输入:一组成绩 [85, 90, 78, 92, 88]
- 预期输出:平均分:86.6
- 提示:参考项目 2 的 calculate_average 函数
练习 4(5 分钟):进阶题 - 串起两个项目
- 输入:用项目 2 的账单统计 + 项目 3 的待办管理,实现一个"记账待办"功能
- 预期输出:能添加消费记录,并标记某笔账已付款
- 提示:两个函数可以独立工作,组合在一起需要共享数据
练习 5(5 分钟):挑战题 - 找 bug
- 输入:以下代码运行后输出什么?
def process(items=[]):
items.append("x")
return items
print(process())
print(process())
- 预期输出:
['x']和['x', 'x'] - 提示:为什么会这样?怎么修?
📝 作业:做一个「个人小工具箱」
需求描述:做一个命令行小工具箱,提供 3-5 个常用函数工具供用户选择使用。
功能点:
1. 计算器:加减乘除,支持小数
2. 单位转换:比如公里↔英里、公斤↔磅
3. 字符串工具:统计字数、反转字符串、判断回文
4. 至少再实现 1 个你自己想加的功能
加分项:
1. 用 while True 循环让工具箱一直可用
2. 给每个函数加上 docstring
验收标准:
- 能跑起来
- 输入有互动提示
- 每个工具函数都有 return 返回值(不是直接 print)
提交方式:评论区贴代码或 GitHub 链接
📚 总结
本文学了 3 个核心点:
- 函数是代码的"封装":把重复逻辑写成函数,一次定义,多次调用
- 参数让函数更灵活:默认参数、多返回值、值传递 vs 引用传递
- return 交货,print 展示:需要返回结果用 return,只需要看用 print
延伸学习资源:
- Python 官方文档 - 定义函数:权威、详细
- 《Python 编程:从入门到实践》第 4 章:实战导向,适合巩固
- 视频:B 站小甲鱼《零基础入门学习 Python》第 38-40 集
互动钩子:
🎯 你在工作中有没有遇到过"同样的代码复制了 N 遍"的场景?后来是怎么解决的?评论区聊聊,老粉优先回复!
下章剧透:
📖 学会了
def定义函数,下一章我们要学习函数的"灵魂"——参数。默认参数、位置参数、关键字参数、不定参数……掌握了这些,你写的函数就能像瑞士军刀一样灵活多变!
(全文约 5200 字)

评论(0)