第1章 1.4 函数与模块:从独自作战到团队协作

🎯 开场:先看一个血泪教训

想象一下:小明写了一个 500 行的 Python 程序,所有代码全塞在一个文件里。第 800 行有个 bug,小明改完,第 200 行的功能居然坏了——气得小明把电脑砸了。

你可能也遇到过:代码一长就乱成一锅粥,改这里坏那里。

上一章我们学了循环和判断,能处理批量数据了。但光会「一个人干活」不够,真正厉害的程序员懂得「分工合作」

学完这一章,你就能:
- 把大程序拆成小零件,想用哪个就用哪个
- 别人写好的工具,直接「拿来主义」
- 代码复用率提升 10 倍,改 bug 不用翻遍全文件


🧱 基础 25 分钟:核心概念

什么是函数?你可以把函数想象成「自动贩卖机」

投入原料 → 机器内部加工 → 输出产品

你不需要知道里面怎么运转,投钱 + 选按钮 = 拿到东西

为什么要用函数?
- 避免重复代码写了 100 遍
- 出问题了只改一个地方
- 代码看起来干净利落
\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\n\n

第一个函数:Say Hello

def greet(name):
"""打招呼的函数"""
print(f"你好,{name}!欢迎学习 Python!")

# 调用函数
greet("小明")
greet("小红")
你好,小明!欢迎学习 Python!
你好,小红!欢迎学习 Python!

def 就是「定义」的缩写,greet 是函数名,name 是参数(原料),print 是输出(产品)。

函数还能返回值:不只是打印

def add(a, b):
"""计算两个数的和"""
result = a + b
return result

# 用变量接收返回值
total = add(3, 5)
print(f"3 + 5 = {total}")
3 + 5 = 8

return 就是「把东西还给你」,不像 print 只是「显示给你看」。

参数默认值:让函数更灵活

def make_coffee(coffee="美式", size="中杯"):
"""做咖啡,默认美式中杯"""
return f"{size}的{coffee},好了!"

print(make_coffee())              # 用默认参数
print(make_coffee("拿铁"))        # 改咖啡类型
print(make_coffee("摩卡", "大杯")) # 全部自定义
中杯的美式,好了!
中杯的拿铁,好了!
大杯的摩卡,好了!

生活类比:点麦当劳,套餐默认薯条可乐,但你非要点麦乐鸡也行。

什么是模块?模块就是一个「工具箱」

Python 自带很多工具箱(模块),你想用哪个就「导入」哪个。

import math

# 用 math 工具箱里的 sqrt(开平方根)
print(math.sqrt(16))   # 4.0
print(math.pi)         # 3.141592653589793
4.0
3.141592653589793

为什么要用模块?
- 不用自己造轮子
- 别人测试好的代码,直接用
- 分工明确,各管各的

常用标准模块一览

import random    # 随机数
import datetime  # 日期时间
import os        # 操作系统相关

# 随机抽一个
print(random.choice(["小明", "小红", "小刚"]))

# 今天的日期
print(datetime.date.today())

# 当前目录
print(os.getcwd())
小红
2026-06-26
/Users/apple/workspace

第三方模块:更强大的工具箱

Python 最厉害的地方是有几十万个第三方库!

# 安装:pip install requests
import requests

# 发送网络请求
response = requests.get("https://httpbin.org/get")
print(response.status_code)
print(response.json())
200
{'args': {}, 'headers': {'Accept': '*/*', 'Host': 'httpbin.org', ...}, 'origin': '...', 'url': 'https://httpbin.org/get'}

自己写模块:把函数存起来

创建一个文件 my_tools.py

# my_tools.py
def calculate_area(width, height):
"""计算矩形面积"""
return width * height

def calculate_perimeter(width, height):
"""计算矩形周长"""
return 2 * (width + height)

在另一个文件里用:

# main.py
from my_tools import calculate_area, calculate_perimeter

w, h = 10, 5
print(f"面积:{calculate_area(w, h)}")
print(f"周长:{calculate_perimeter(w, h)}")
面积:50
周长:30

这就是「组件化」思维:把常用的功能打包,用的时候直接导入。


🔥 实战 35 分钟:3 个递进小项目

项目 1(5 分钟):温度转换器

def celsius_to_fahrenheit(c):
"""摄氏转华氏"""
return c * 9/5 + 32

def fahrenheit_to_celsius(f):
"""华氏转摄氏"""
return (f - 32) * 5/9

# 测试几个温度
temps = [0, 100, 37, -40]
print("摄氏 → 华氏")
for t in temps:
print(f"{t}°C = {celsius_to_fahrenheit(t):.1f}°F")
摄氏 → 华氏
0°C = 32.0°F
100°C = 212.0°F
37°C = 98.6°F
-40°C = -40.0°F

学到了啥:函数封装 + 参数传递 + 循环调用


项目 2(15 分钟):个人账单统计工具

import datetime

def parse_expense(line):
"""解析一行账单,返回 (日期, 类别, 金额)"""
parts = line.strip().split(",")
date = datetime.datetime.strptime(parts[0], "%Y-%m-%d")
category = parts[1]
amount = float(parts[2])
return date, category, amount

def analyze_expenses(expenses):
"""分析账单,返回统计结果"""
total = 0
by_category = {}

for date, category, amount in expenses:
    total += amount
    by_category[category] = by_category.get(category, 0) + amount

return total, by_category

# 模拟 CSV 数据
csv_data = """2026-06-01,餐饮,45.5
2026-06-02,交通,12.0
2026-06-03,购物,299.0
2026-06-04,餐饮,68.0
2026-06-05,交通,8.5
2026-06-06,餐饮,52.0"""

# 解析所有行
expenses = [parse_expense(line) for line in csv_data.strip().split("\n")]

# 分析
total, by_category = analyze_expenses(expenses)

print(f"=== 6月账单统计 ===")
print(f"总支出:{total:.2f} 元")
print(f"\n分类明细:")
for cat, amount in sorted(by_category.items(), key=lambda x: -x[1]):
print(f"  {cat}: {amount:.2f} 元 ({amount/total*100:.1f}%)")
=== 6月账单统计 ===
总支出:485.00 元

分类明细:
餐饮: 165.50 元 (34.1%)
购物: 299.00 元 (61.6%)
交通: 20.50 元 (4.2%)

学到了啥
- 函数拆分(解析 + 分析)
- 列表推导式快速处理多行数据
- 模块导入(datetime)


项目 3(15 分钟):待办事项小工具

import json
from datetime import datetime

class TodoList:
"""待办清单管理器"""

def __init__(self, filename="todos.json"):
    self.filename = filename
    self.todos = self._load()

def _load(self):
    """加载文件"""
    try:
        with open(self.filename, "r", encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        return []

def _save(self):
    """保存文件"""
    with open(self.filename, "w", encoding="utf-8") as f:
        json.dump(self.todos, f, ensure_ascii=False, indent=2)

def add(self, task, priority="中"):
    """添加待办"""
    todo = {
        "id": len(self.todos) + 1,
        "task": task,
        "priority": priority,
        "done": False,
        "created": datetime.now().strftime("%Y-%m-%d %H:%M")
    }
    self.todos.append(todo)
    self._save()
    print(f"✅ 已添加:「{task}」")

def list(self):
    """列出所有待办"""
    if not self.todos:
        print("📝 清单是空的!")
        return

    priority_map = {"高": "🔴", "中": "🟡", "低": "🟢"}
    print(f"\n📋 当前待办 ({len(self.todos)} 项):")
    for todo in self.todos:
        status = "✅" if todo["done"] else "⬜"
        icon = priority_map.get(todo["priority"], "⚪")
        print(f"  {icon} [{todo['id']}] {status} {todo['task']}")

def done(self, task_id):
    """标记完成"""
    for todo in self.todos:
        if todo["id"] == task_id:
            todo["done"] = True
            self._save()
            print(f"🎉 「{todo['task']}」完成!")
            return
    print(f"❌ 没找到 ID 为 {task_id} 的待办")

# 使用
if __name__ == "__main__":
todo_list = TodoList()

# 添加几个待办
todo_list.add("买水果", priority="高")
todo_list.add("写周报", priority="中")
todo_list.add("整理桌面", priority="低")

# 列出所有
todo_list.list()

# 标记完成
todo_list.done(1)
todo_list.list()
✅ 已添加:「买水果」
✅ 已添加:「写周报」
✅ 已添加:「整理桌面」

📋 当前待办 (3 项):
🔴 [1] ⬜ 买水果
🟡 [2] ⬜ 写周报
🟢 [3] ⬜ 整理桌面
🎉 「买水果」完成!

📋 当前待办 (3 项):
🔴 [1] ✅ 买水果
🟡 [2] ⬜ 写周报
🟢 [3] ⬜ 整理桌面

学到了啥
- 类(class)的概念:把数据和函数打包
- 文件读写持久化
- __init__ 构造函数


💪 进阶 20 分钟:常见坑 + 调试技巧

坑 1:参数传递的是引用,不是副本

# ❌ 错误示例
def add_item(bad_list):
bad_list.append("新东西")  # 修改了原列表!

my_list = ["原始"]
add_item(my_list)
print(my_list)  # ['原始', '新东西'] - 原列表被改了!
# ✅ 正确示例
def add_item_fixed(good_list):
new_list = good_list.copy()  # 先复制一份
new_list.append("新东西")
return new_list  # 返回新列表

my_list = ["原始"]
result = add_item_fixed(my_list)
print(f"原列表:{my_list}")       # ['原始']
print(f"新列表:{result}")       # ['原始', '新东西']

坑 2:默认参数别用可变对象

# ❌ 错误示例
def bad_function(items=[]):  # 列表是 可变 对象!
items.append("item")
return items

print(bad_function())  # ['item']
print(bad_function())  # ['item', 'item'] - 累积了!
print(bad_function())  # ['item', 'item', 'item']
# ✅ 正确示例
def good_function(items=None):
if items is None:
    items = []  # 每次创建新列表
items.append("item")
return items

print(good_function())  # ['item']
print(good_function())  # ['item']
print(good_function())  # ['item']

原理:默认参数在函数定义时就创建了,不是每次调用时创建。

坑 3:导入模块时的路径问题

# ❌ 可能报错
import my_module  # 如果当前目录没有,Python 找不到

# ✅ 指定路径
import sys
sys.path.append("/path/to/your/module")
import my_module

坑 4:忘了 return 就拿不到结果

# ❌ 错误示例
def calculate(x):
x * 2  # 算完了但没 return!

result = calculate(5)
print(result)  # None - 白算了!
# ✅ 正确示例
def calculate_fixed(x):
return x * 2

result = calculate_fixed(5)
print(result)  # 10

坑 5:类的方法第一个参数必须是 self

# ❌ 错误示例
class Calculator:
def add(a, b):  # 忘了 self!
    return a + b

calc = Calculator()
calc.add(1, 2)  # 会报错
# ✅ 正确示例
class Calculator:
def add(self, a, b):  # self 必须是第一个参数
    return a + b

calc = Calculator()
print(calc.add(1, 2))  # 3

调试技巧:print 大法 + pdb

# 简单调试:print 打印中间值
def buggy_function(n):
result = 1
for i in range(n):
    result = result * i
    print(f"i={i}, result={result}")  # 看看每一步的值
return result

# 专业调试:用 pdb
import pdb

def another_function(x):
pdb.set_trace()  # 程序会停在这里,等待你输入命令
return x * 2

pdb 常用命令:
- n - 执行下一行
- p 变量名 - 打印变量值
- c - 继续执行直到断点
- q - 退出调试


✏️ 练习题 + 作业题

练习题(5 道,10 分钟)

练习 1(1 分钟):改函数调用

def greet(name, age):
return f"我叫{name},今年{age}岁"

# 修改下面这行,让输出变成 "我叫小红,今年18岁"
result = ???
print(result)
  • 预期输出:我叫小红,今年18岁
  • 提示:直接传入两个参数

练习 2(2 分钟):加个判断

def check_age(age):
# 当 age >= 18 时返回 "成年人",否则返回 "未成年"
???
return ???

print(check_age(20))
print(check_age(16))
  • 预期输出:成年人未成年
  • 提示:用 if 判断 age 和 18 的大小

练习 3(2 分钟):处理新数据
用项目 2 的 parse_expense 函数解析这行数据:

line = "2026-06-15,娱乐,199.9"
# 调用 parse_expense 解析它,打印出日期、类别、金额
  • 预期输出:日期对象、类别字符串、金额浮点数
  • 提示:函数返回的是元组,用多个变量接收

练习 4(3 分钟):改造 TodoList
TodoList 类添加一个 delete 方法,删除指定 ID 的待办:

# 参考 done 方法的实现
def delete(self, task_id):
???
  • 预期输出:删除后列表里不再有这个待办
  • 提示:遍历列表,找 ID,移除

练习 5(2 分钟):看报错猜原因

def process(data):
return data.append("new")

my_data = ["a", "b"]
result = process(my_data)
print(result)
  • 预期输出:None
  • 提示:append 返回什么?

作业题(30 分钟-2 小时)

作业:做一个「个人账本工具」

  • 需求描述:做一个命令行账本,能记账、查账、统计
  • 功能点
    1. 添加一笔支出(日期、类别、金额、备注)
    2. 查看所有支出列表
    3. 按月份统计总支出
    4. 数据保存到本地文件,程序重启不丢失
  • 加分项
    1. 支持收入记账(和支出分开统计)
    2. 按类别筛选查看
  • 验收标准
  • 能运行不报错
  • 添加后重启程序数据还在
  • 统计结果正确
  • 代码有注释说明
  • 提交方式:评论区贴代码或 GitHub 链接

📚 总结 + 资源

一句话总结:函数是代码的「积木块」,模块是函数的「工具箱」,学会分工协作,代码才能又长又稳。

延伸学习
- 官方文档:https://docs.python.org/zh-cn/3/tutorial/
- 《Python编程:从入门到实践》- 第 3-6 章
- 视频:B 站小甲鱼 Python 教程(第 15-20 集)

互动钩子:你在写代码时有没有「一个人写了几百行,改一处坏三处」的惨痛经历?评论区聊聊,老粉优先回复!


下章剧透:学会了函数和模块,下一章我们要让「不同的零件」能互相传递消息——组件之间怎么通信?数据怎么从父组件传到子组件? 敬请期待!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。