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

举报
倔强的石头_ 发表于 2025/12/17 15:42:52 2025/12/17
【摘要】 @[toc]欢迎回到「Python 从入门到实战」系列专栏。上一篇咱们掌握了字典,能高效存储 “键 - 值关联” 的结构化数据 —— 比如商品的 “名称 - 价格 - 库存”。但在实际编程中,你会发现很多逻辑会重复出现:比如在商品库存管理系统里,“计算所有商品总价值”“筛选缺货商品” 这些操作,如果在多个地方需要用,每次都写一遍相同的循环和判断,不仅代码冗余,改的时候还要到处改,很容易出错。...

image.png

@[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=Noneage参数可选(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会提示),报错时先检查拼写。

六、小结与下一篇预告

这篇你学到了什么?

  1. 函数基础:用def定义函数,理解形参、实参、返回值,会调用简单函数;
  2. 参数传递:掌握位置实参(按顺序)、关键字实参(指定名)、默认值(可选参数),避免参数顺序错误;
  3. 返回值:函数可返回字符串、列表、字典,用return产出结果,处理可选实参;
  4. 函数与数据结构:传递列表 / 字典给函数,批量处理数据,比如筛选、统计;
  5. 模块化:把函数放在模块里,导入使用(import/from...import),让代码整洁;
  6. 避坑要点:默认值位置、return 不要漏、列表传递的修改问题、拼写错误。

下一篇预告

今天的函数让代码实现了 “复用”,但当需要处理 “相似但有差异” 的对象(比如 “学生” 和 “教师”,都有姓名年龄,但学生有成绩,教师有工资)时,函数 + 字典的组合会比较繁琐。下一篇咱们会学 Python 的 “面向对象核心”——,它能把 “数据(属性)” 和 “操作(方法)” 封装在一起,更灵活地模拟现实中的对象,为后面的复杂项目(比如游戏、Web 应用)打基础。

如果这篇内容帮你掌握了函数,欢迎在评论区分享你的 “函数模块”(比如自己写一个处理成绩的模块),咱们一起交流进步~

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。