Python 从入门到实战(七):函数(代码复用的 “万能封装器”)

@[toc]
欢迎回到「Python 从入门到实战」系列专栏。上一篇咱们掌握了字典,能高效存储 “键 - 值关联” 的结构化数据 —— 比如商品的 “名称 - 价格 - 库存”。但在实际编程中,你会发现很多逻辑会重复出现:比如在商品库存管理系统里,“计算所有商品总价值”“筛选缺货商品” 这些操作,如果在多个地方需要用,每次都写一遍相同的循环和判断,不仅代码冗余,改的时候还要到处改,很容易出错。
今天咱们要学 Python 的 “代码复用神器”——函数(Function)。它就像一个 “万能封装器”,能把重复的逻辑打包成一个 “工具”,需要用的时候调用就行,不用每次都写一遍。学会函数,你的代码会从 “杂乱的面条” 变成 “模块化的积木”,既简洁又好维护。
一、为什么需要函数?先看 “重复代码” 的痛点
在学函数语法前,先通过一个场景感受函数的价值。比如咱们要处理两组成绩列表,都需要筛选出及格成绩(>60)并计算平均分:
没有函数:重复写相同逻辑
python
运行
# 处理第一组成绩
scores1 = [85, 58, 92, 76, 45]
passed1 = []
for score in scores1:
if score > 60:
passed1.append(score)
avg1 = sum(passed1) / len(passed1) if passed1 else 0
print(f"第一组及格成绩:{passed1},平均分:{avg1:.1f}")
# 处理第二组成绩(逻辑和上面完全一样,只是换了列表)
scores2 = [90, 65, 52, 88, 70]
passed2 = []
for score in scores2:
if score > 60:
passed2.append(score)
avg2 = sum(passed2) / len(passed2) if passed2 else 0
print(f"第二组及格成绩:{passed2},平均分:{avg2:.1f}")
这段代码能运行,但有个大问题:筛选及格成绩、算平均分的逻辑写了两遍。如果要处理 10 组成绩,就要写 10 遍相同的代码,改的时候(比如把及格线改成 65)还要改 10 处,效率极低。
有函数:封装逻辑,调用即可
用函数把 “处理成绩” 的逻辑封装起来,需要的时候传不同的列表就行:
python
运行
# 封装处理成绩的逻辑成函数
def process_scores(scores):
# 筛选及格成绩
passed = [s for s in scores if s > 60]
# 计算平均分
avg = sum(passed) / len(passed) if passed else 0
# 返回结果(及格列表和平均分)
return passed, avg
# 处理第一组成绩(调用函数)
scores1 = [85, 58, 92, 76, 45]
passed1, avg1 = process_scores(scores1)
print(f"第一组及格成绩:{passed1},平均分:{avg1:.1f}")
# 处理第二组成绩(再调用一次函数)
scores2 = [90, 65, 52, 88, 70]
passed2, avg2 = process_scores(scores2)
print(f"第二组及格成绩:{passed2},平均分:{avg2:.1f}")
结果和之前一样,但代码简洁多了:处理逻辑只写了一遍,要改及格线的话,只改函数里的>60就行。这就是函数的核心价值:封装重复逻辑,实现代码复用,降低维护成本。
二、函数基础:定义与调用(从简单到复杂)
函数的本质是 “带名字的代码块”,定义后可以通过名字调用。咱们从最基础的 “无参函数” 开始,逐步过渡到 “带参函数”“有返回值的函数”,每个步骤都配清晰示例。
1. 定义函数:用def关键字
定义函数的基本语法很简单,核心是def关键字、函数名、参数列表和函数体:
python
运行
def 函数名(参数列表):
"""函数文档字符串(描述功能)"""
函数体(要执行的代码,注意缩进)
[return 返回值] # 可选,没有则返回None
def:告诉 Python “这是一个函数”;- 函数名:见名知意,用小写 + 下划线(蛇形命名法),比如
greet_user; - 参数列表:函数需要的输入,没有就空着,比如
(username); - 文档字符串:用三引号包裹,描述函数功能,方便自己和别人理解;
- 函数体:要执行的代码,必须缩进(4 个空格);
- return:可选,返回函数的结果,没有的话默认返回
None。
▶ 示例 1:无参函数(不需要输入,直接执行)
比如一个简单的问候函数,不需要参数,直接打印问候语:
python
运行
# 定义无参函数
def greet():
"""显示简单的问候语"""
print("Hello! Welcome to Python world!")
# 调用函数(函数名+括号)
greet() # 输出:Hello! Welcome to Python world!
调用函数时,必须写括号greet(),哪怕没有参数。如果只写greet,不会执行函数,只会显示函数的内存地址(比如<function greet at 0x0000021F7A8D1E20>)。
▶ 示例 2:带参函数(需要输入才能执行)
比如根据用户名问候,需要传入username参数:
python
运行
# 定义带参函数(参数username)
def greet_user(username):
"""根据用户名显示个性化问候"""
print(f"Hello, {username.title()}!")
# 调用函数(传入实参"zhangsan")
greet_user("zhangsan") # 输出:Hello, Zhangsan!
greet_user("lisi") # 输出:Hello, Lisi!
这里的username是形参(函数定义时的参数,相当于 “占位符”),调用时传入的"zhangsan"是实参(实际的输入值)。Python 会把实参赋值给形参,然后执行函数体。
2. 参数传递:三种常见方式
参数传递是函数的核心,新手容易在 “传参顺序”“默认值” 上踩坑。咱们讲三种最常用的传参方式,每种都用示例说明。
▶ 方式 1:位置实参(按顺序匹配)
最基础的传参方式,实参的顺序必须和形参的顺序一致。比如定义一个描述宠物的函数,需要animal_type(动物类型)和pet_name(名字)两个参数:
python
运行
# 定义带两个形参的函数
def describe_pet(animal_type, pet_name):
"""描述宠物的类型和名字"""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
# 位置实参:按顺序传值(先animal_type,再pet_name)
describe_pet("hamster", "harry") # 输出:I have a hamster. My hamster's name is Harry.
如果顺序错了,结果会很奇怪:
python
运行
# 错误示例:顺序颠倒
describe_pet("harry", "hamster") # 输出:I have a harry. My harry's name is Hamster.(逻辑错误)
避坑:位置实参必须 “形参顺序 = 实参顺序”,不确定的时候用关键字实参。
▶ 方式 2:关键字实参(指定参数名,顺序无关)
关键字实参直接指定 “形参名 = 实参值”,不需要按顺序,适合参数较多的情况:
python
运行
# 关键字实参:指定参数名,顺序无关
describe_pet(animal_type="dog", pet_name="willie") # 正确
describe_pet(pet_name="willie", animal_type="dog") # 也正确,顺序不影响
输出都是:
plaintext
I have a dog.
My dog's name is Willie.
注意:关键字实参的参数名必须和函数定义的形参名完全一致,比如写成animal="dog"会报错(形参是animal_type)。
▶ 方式 3:默认值(让参数可选)
如果某个参数大部分情况下都是同一个值,可以给它设默认值,调用时如果不传这个参数,就用默认值;传了就用传入的值。比如大部分宠物是狗,给animal_type设默认值"dog":
python
运行
# 定义带默认值的函数(默认值参数必须放在后面)
def describe_pet(pet_name, animal_type="dog"): # 默认值参数在后面
"""描述宠物,默认是狗"""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
# 调用1:不传animal_type,用默认值"dog"
describe_pet("willie") # 输出:I have a dog. My dog's name is Willie.
# 调用2:传animal_type,覆盖默认值
describe_pet("harry", "hamster") # 输出:I have a hamster. My hamster's name is Harry.
关键规则:带默认值的参数必须放在 “无默认值参数” 的后面,否则会报错。比如def describe_pet(animal_type="dog", pet_name)会报错(默认值参数在前面)。
3. 返回值:让函数 “产出结果”
函数不仅能 “做事”(比如打印),还能 “产出结果”(比如计算后返回值),用return语句实现。返回值可以是字符串、数字、列表、字典,甚至另一个函数。
▶ 示例 1:返回简单值(字符串)
比如函数接收名和姓,返回格式化的全名:
python
运行
# 定义返回字符串的函数
def get_full_name(first_name, last_name):
"""返回格式为"First Last"的全名"""
full_name = f"{first_name} {last_name}"
return full_name.title() # 返回首字母大写的结果
# 调用函数,接收返回值
musician = get_full_name("jimi", "hendrix")
print(musician) # 输出:Jimi Hendrix
▶ 示例 2:返回字典(复杂数据)
比如函数接收用户信息,返回包含用户详情的字典:
python
运行
# 定义返回字典的函数
def build_user(first_name, last_name, age=None): # age可选,默认None
"""返回包含用户信息的字典,age可选"""
user = {
"first": first_name.title(),
"last": last_name.title()
}
# 如果传入age,添加到字典
if age:
user["age"] = age
return user
# 调用1:不传age
user1 = build_user("albert", "einstein")
print(user1) # 输出:{'first': 'Albert', 'last': 'Einstein'}
# 调用2:传age
user2 = build_user("marie", "curie", age=28)
print(user2) # 输出:{'first': 'Marie', 'last': 'Curie', 'age': 28}
这里用age=None让age参数可选(None表示 “没有值”),在函数体内判断如果有age就添加到字典,灵活处理可选参数。
▶ 示例 3:返回处理后的列表
比如函数接收成绩列表,返回及格且排序后的成绩:
python
运行
# 定义返回列表的函数
def process_scores(scores):
"""接收成绩列表,返回及格(>60)且排序后的列表"""
# 筛选及格成绩
passed = [s for s in scores if s > 60]
# 排序
passed.sort(reverse=True) # 降序排序
return passed
# 调用函数
scores = [85, 58, 92, 76, 45, 95]
sorted_passed = process_scores(scores)
print(f"及格成绩(降序):{sorted_passed}") # 输出:及格成绩(降序):[95, 92, 85, 76]
三、函数进阶:与数据结构结合 + 模块化
函数的真正威力在于和列表、字典结合,处理批量数据;以及模块化(把函数放在单独文件里),让代码更整洁。
1. 函数与列表:批量处理数据
传递列表给函数,能批量处理列表中的元素,比如修改列表、筛选元素、统计数据。
▶ 示例:批量处理待办事项
定义函数process_todos(todos),接收待办列表,返回 “已完成” 和 “未完成” 的列表:
python
运行
def process_todos(todos):
"""接收待办列表(每个元素是"状态: 内容"),返回已完成和未完成列表"""
completed = []
pending = []
for todo in todos:
status, content = todo.split(": ", 1) # 分割成状态和内容
if status == "完成":
completed.append(content)
else:
pending.append(content)
return completed, pending
# 待办列表
todos = [
"完成: 写函数文章",
"未完成: 整理示例代码",
"未完成: 检查避坑点",
"完成: 写小结"
]
# 调用函数
completed_todos, pending_todos = process_todos(todos)
print(f"已完成:{completed_todos}") # 输出:已完成:['写函数文章', '写小结']
print(f"未完成:{pending_todos}") # 输出:未完成:['整理示例代码', '检查避坑点']
2. 函数与字典:处理结构化数据
传递字典给函数,能批量处理字典中的键值对,比如统计字典中的特定值。
▶ 示例:统计商品总价值
定义函数calculate_total_value(products),接收商品字典列表,返回总价值:
python
运行
def calculate_total_value(products):
"""接收商品列表(每个元素是商品字典),计算总价值(价格×库存)"""
total = 0
for product in products:
value = product["price"] * product["stock"]
total += value
return round(total, 2) # 保留2位小数
# 商品列表
products = [
{"name": "苹果", "price": 5.99, "stock": 100},
{"name": "香蕉", "price": 3.99, "stock": 45}
]
# 调用函数
total = calculate_total_value(products)
print(f"商品总价值:{total}元") # 输出:商品总价值:788.55元
3. 函数模块化:导入与复用
当函数越来越多,把它们放在单独的.py文件(称为 “模块”)里,主程序导入使用,能让代码更整洁。比如创建product_functions.py模块,存放商品相关的函数。
▶ 步骤 1:创建模块(product_functions.py)
python
运行
# product_functions.py(模块文件)
def print_inventory(products):
"""打印商品库存清单"""
print(f"{'商品名':<8}{'价格(元)':<10}{'库存(件)':<10}")
print("-" * 30)
for p in products:
print(f"{p['name']:<8}{p['price']:<10.2f}{p['stock']:<10}")
def update_stock(products, name, count):
"""更新商品库存(count为正表示进货,负表示出货)"""
for p in products:
if p["name"] == name:
p["stock"] += count
return True # 更新成功
return False # 商品不存在
▶ 步骤 2:主程序导入模块并调用
python
运行
# main.py(主程序)
# 导入模块
import product_functions as pf # 给模块起别名pf,方便调用
# 商品列表
products = [
{"name": "苹果", "price": 5.99, "stock": 100},
{"name": "香蕉", "price": 3.99, "stock": 45}
]
# 调用模块中的函数
print("【初始库存】")
pf.print_inventory(products) # 调用print_inventory
# 更新库存(苹果出货5件,count=-5)
success = pf.update_stock(products, "苹果", -5)
if success:
print("\n【更新后库存】")
pf.print_inventory(products)
else:
print("\n商品不存在")
运行main.py,输出:
plaintext
【初始库存】
商品名 价格(元) 库存(件)
------------------------------
苹果 5.99 100
香蕉 3.99 45
【更新后库存】
商品名 价格(元) 库存(件)
------------------------------
苹果 5.99 95
香蕉 3.99 45
常见导入方式
除了导入整个模块,还可以导入特定函数:
python
运行
# 导入特定函数
from product_functions import print_inventory, update_stock
print_inventory(products) # 直接调用,不用模块名
# 导入函数并起别名(避免重名)
from product_functions import print_inventory as pi
pi(products)
# 导入模块中所有函数(不推荐,可能重名)
from product_functions import *
print_inventory(products)
四、综合实操:重构商品库存管理系统
咱们用函数重构第六篇的 “商品库存管理系统”,把核心功能(打印库存、更新库存、筛选缺货、计算总价值)都封装成函数,让主程序更简洁。
完整代码
1. 模块文件:inventory_functions.py
python
运行
# inventory_functions.py(库存管理函数模块)
def print_inventory(products):
"""打印商品库存清单"""
print(f"{'商品名':<8}{'价格(元)':<10}{'库存(件)':<10}")
print("-" * 30)
for product in products:
print(f"{product['name']:<8}{product['price']:<10.2f}{product['stock']:<10}")
def update_stock(products, product_name, change_count):
"""
更新商品库存
products: 商品列表
product_name: 商品名
change_count: 变动数量(正=进货,负=出货)
返回:True=成功,False=商品不存在,None=库存不足
"""
for product in products:
if product["name"] == product_name:
# 出货时检查库存
if change_count < 0 and product["stock"] < abs(change_count):
return None # 库存不足
product["stock"] += change_count
return True
return False # 商品不存在
def filter_shortage(products, threshold=50):
"""筛选库存低于threshold的缺货商品,默认阈值50"""
return [p for p in products if p["stock"] < threshold]
def calculate_total_value(products):
"""计算所有商品的总价值"""
total = sum(p["price"] * p["stock"] for p in products)
return round(total, 2)
2. 主程序:main.py
python
运行
# main.py(主程序)
from inventory_functions import (
print_inventory, update_stock,
filter_shortage, calculate_total_value
)
def main():
print("===== 商品库存管理系统 =====")
# 初始化商品列表
products = [
{"name": "苹果", "price": 5.99, "stock": 100},
{"name": "香蕉", "price": 3.99, "stock": 45},
{"name": "橙子", "price": 4.5, "stock": 150},
{"name": "葡萄", "price": 7.99, "stock": 30}
]
# 1. 打印初始库存
print("\n【1. 初始库存清单】")
print_inventory(products)
# 2. 更新库存(苹果出货5件)
print("\n【2. 更新库存】")
result = update_stock(products, "苹果", -5)
if result is True:
print("✅ 苹果出货5件,更新成功!")
elif result is None:
print("❌ 苹果库存不足,更新失败!")
else:
print("❌ 未找到苹果,更新失败!")
# 3. 打印更新后库存
print("\n【3. 更新后库存清单】")
print_inventory(products)
# 4. 筛选缺货商品
print("\n【4. 缺货商品清单】(库存<50件)")
shortage = filter_shortage(products)
if shortage:
for p in shortage:
print(f"- {p['name']}: {p['stock']}件")
else:
print("✅ 无缺货商品!")
# 5. 计算总价值
print("\n【5. 库存总价值】")
total = calculate_total_value(products)
print(f"💰 所有商品总价值:{total}元")
print("\n======================")
# 运行主函数
if __name__ == "__main__":
main()
运行结果
plaintext
===== 商品库存管理系统 =====
【1. 初始库存清单】
商品名 价格(元) 库存(件)
------------------------------
苹果 5.99 100
香蕉 3.99 45
橙子 4.50 150
葡萄 7.99 30
【2. 更新库存】
✅ 苹果出货5件,更新成功!
【3. 更新后库存清单】
商品名 价格(元) 库存(件)
------------------------------
苹果 5.99 95
香蕉 3.99 45
橙子 4.50 150
葡萄 7.99 30
【4. 缺货商品清单】(库存<50件)
- 香蕉: 45件
- 葡萄: 30件
【5. 库存总价值】
💰 所有商品总价值:1649.15元
======================
重构后的代码优势很明显:主程序只负责 “调用函数和组织流程”,核心逻辑都在模块里,要修改某个功能(比如把缺货阈值改成 40),只改filter_shortage函数的默认值就行,不用动主程序。
五、新手必踩的 5 个坑:避坑指南
函数虽然好用,但新手容易在细节上出错,导致逻辑错误或报错。咱们总结 5 个高频坑点:
坑 1:参数顺序错误(位置实参)
python
运行
# 错误示例:参数顺序颠倒
def build_full_name(first, last):
return f"{first} {last}"
name = build_full_name("Doe", "John") # 应该是("John", "Doe")
print(name) # 输出:Doe John(错误,应该是John Doe)
解决:不确定顺序时,用关键字实参:build_full_name(first="John", last="Doe")。
坑 2:默认值参数位置错误
python
运行
# 错误示例:默认值参数在无默认值参数前面
def make_shirt(size="L", message): # 报错:SyntaxError: non-default argument follows default argument
print(f"Shirt size: {size}, message: {message}")
规则:带默认值的参数必须放在 “无默认值参数” 的后面,正确写法:def make_shirt(message, size="L")。
坑 3:忘记写 return,导致返回 None
python
运行
# 错误示例:忘记写return,默认返回None
def add(a, b):
result = a + b # 没有return
sum_result = add(2, 3)
print(sum_result) # 输出:None(错误,应该是5)
解决:需要返回值时,必须写return result。
坑 4:传递列表时 “意外修改原列表”
函数修改传递的列表时,会直接修改原列表(因为列表是 “可变类型”):
python
运行
# 示例:函数修改原列表
def remove_first_item(my_list):
my_list.pop(0) # 修改列表
original_list = [1, 2, 3]
remove_first_item(original_list)
print(original_list) # 输出:[2, 3](原列表被修改)
如果不想修改原列表:传递列表的副本remove_first_item(original_list[:])。
坑 5:函数名或参数名拼写错误
python
运行
# 错误示例:函数名拼写错误(greet_user写成greet_users)
def greet_user(username):
print(f"Hello, {username}!")
greet_users("zhangsan") # 报错:NameError: name 'greet_users' is not defined
解决:用编辑器的自动补全功能(比如 VS Code 输入greet会提示),报错时先检查拼写。
六、小结与下一篇预告
这篇你学到了什么?
- 函数基础:用
def定义函数,理解形参、实参、返回值,会调用简单函数; - 参数传递:掌握位置实参(按顺序)、关键字实参(指定名)、默认值(可选参数),避免参数顺序错误;
- 返回值:函数可返回字符串、列表、字典,用
return产出结果,处理可选实参; - 函数与数据结构:传递列表 / 字典给函数,批量处理数据,比如筛选、统计;
- 模块化:把函数放在模块里,导入使用(
import/from...import),让代码整洁; - 避坑要点:默认值位置、return 不要漏、列表传递的修改问题、拼写错误。
下一篇预告
今天的函数让代码实现了 “复用”,但当需要处理 “相似但有差异” 的对象(比如 “学生” 和 “教师”,都有姓名年龄,但学生有成绩,教师有工资)时,函数 + 字典的组合会比较繁琐。下一篇咱们会学 Python 的 “面向对象核心”——类,它能把 “数据(属性)” 和 “操作(方法)” 封装在一起,更灵活地模拟现实中的对象,为后面的复杂项目(比如游戏、Web 应用)打基础。
如果这篇内容帮你掌握了函数,欢迎在评论区分享你的 “函数模块”(比如自己写一个处理成绩的模块),咱们一起交流进步~
- 点赞
- 收藏
- 关注作者
评论(0)