Day 07:函数进阶与文件操作

🎯 学习目标:理解作用域与闭包,掌握递归思维,熟练 lambda 和文件操作

1. 函数嵌套调用

函数可以调用另一个函数,形成调用链:

 
def is_even(n):
 
    """判断是否为偶数"""
 
    return n % 2 == 0
 
def filter_evens(numbers):
 
    """从列表中筛选偶数(调用 is_even)"""
 
    result = []
 
    for n in numbers:
 
        if is_even(n):      # 函数嵌套调用
 
            result.append(n)
 
    return result
 
print(filter_evens([1, 2, 3, 4, 5, 6]))  # [2, 4, 6]
 

2. 作用域

2.1 LEGB 规则

Python 查找变量时按 LEGB 顺序:Local → Enclosing → Global → Built-in


graph TB

    L[Local<br/>函数内部] --> E[Enclosing<br/>外层函数]

    E --> G[Global<br/>模块级别]

    G --> B[Built-in<br/>print/len/range]

2.2 四种作用域演示

 
# Global 作用域
 
x = "全局变量"
 
def outer():
 
    # Enclosing 作用域
 
    x = "外层函数的变量"
 
    def inner():
 
        # Local 作用域
 
        x = "内层函数的变量"
 
        print(f"inner: {x}")    # 内层函数的变量
 
    inner()
 
    print(f"outer: {x}")        # 外层函数的变量
 
outer()
 
print(f"global: {x}")           # 全局变量
 

2.3 全局变量与局部变量

 
# 函数内可以读取全局变量,但不能直接修改
 
total = 0
 
def add_one():
 
    # print(total)    # ❌ 下一行有 total= 声明,所以 total 被视为局部变量
 
    total = total + 1  # ❌ UnboundLocalError:局部变量 total 还没赋值就被引用了
 
# 正确做法:用 global 声明
 
def add_one_correct():
 
    global total
 
    total = total + 1
 

2.4 global 和 nonlocal

 
# global:在函数内修改全局变量
 
count = 0
 
def increment():
 
    global count
 
    count += 1
 
increment()
 
print(count)  # 1
 
# nonlocal:在内层函数中修改外层(非全局)变量
 
def outer():
 
    count = 0
 
    def inner():
 
        nonlocal count
 
        count += 1        # 修改的是 outer 的 count,不是全局的
 
        return count
 
    return inner
 
counter = outer()
 
print(counter())  # 1
 
print(counter())  # 2
 
print(counter())  # 3
 

3. 闭包 ⭐

闭包 = 函数 + 它”记住”的外部变量

 
def make_power(exponent):
 
    """创建一个计算 n 次幂的函数"""
 
    def power(base):
 
        return base ** exponent  # exponent 来自外层函数,被"记住"了
 
    return power                # 返回内层函数(闭包)
 
square = make_power(2)   # exponent=2 被记住
 
cube = make_power(3)     # exponent=3 被记住
 
print(square(5))  # 25  (5²)
 
print(square(10)) # 100 (10²)
 
print(cube(5))    # 125 (5³)
 

3.1 闭包三要素

  1. 函数嵌套——内层函数定义在外层函数内部

  2. 引用外部变量——内层函数使用了外层函数的变量

  3. 返回内层函数——外层函数返回内层函数

3.2 闭包的作用

 
# 数据隐藏:外部无法直接访问 count
 
def make_counter():
 
    count = 0              # 私有变量
 
    def counter():
 
        nonlocal count
 
        count += 1
 
        return count
 
    return counter
 
c = make_counter()
 
print(c())  # 1
 
print(c())  # 2
 
# 无法从外部直接访问或修改 count
 

4. 递归

递归 = 函数调用自己

4.1 递归三要素

要素说明
终止条件必须有一个不再递归的条件,否则无限循环
问题递减每次递归,问题规模必须缩小
同类子问题大问题能分解为结构相同的子问题

4.2 阶乘

 
def factorial(n):
 
    """计算 n! = n × (n-1) × ... × 1"""
 
    if n <= 1:            # 终止条件
 
        return 1
 
    return n * factorial(n - 1)  # 问题递减
 
print(factorial(5))  # 120
 
#             = 5 × 4 × 3 × 2 × 1 = 120
 

4.3 斐波那契数列(递归 vs 迭代)

 
# 递归版本(简洁但效率低)
 
def fib_recursive(n):
 
    if n <= 1:
 
        return n
 
    return fib_recursive(n-1) + fib_recursive(n-2)
 
# 迭代版本(推荐)
 
def fib_iterative(n):
 
    a, b = 0, 1
 
    for _ in range(n):
 
        a, b = b, a + b
 
    return a
 

递归的陷阱

Python 默认递归深度限制为 1000。递归太深会导致 RecursionError

大多数递归问题可以用循环更好地解决。

5. lambda 匿名函数

 
# 普通函数
 
def add(x, y):
 
    return x + y
 
# lambda 等价写法
 
add_lambda = lambda x, y: x + y
 
# lambda 常用于排序、过滤、映射
 
students = [("张三", 85), ("李四", 92), ("王五", 78)]
 
# 按成绩排序
 
students.sort(key=lambda s: s[1])
 
print(students)  # [('王五', 78), ('张三', 85), ('李四', 92)]
 
# filter:筛选偶数
 
nums = [1, 2, 3, 4, 5, 6]
 
evens = list(filter(lambda x: x % 2 == 0, nums))
 
print(evens)  # [2, 4, 6]
 
# map:全部平方
 
squared = list(map(lambda x: x**2, nums))
 
print(squared)  # [1, 4, 9, 16, 25, 36]
 
# lambda x: if x > 0: return x  ← 这种复杂逻辑还是用 def
 

6. 函数注释(Type Hints 入门)

 
def calculate(weight: float, height: float) -> float:
 
    """
 
    计算 BMI
 
    :param weight: 体重(千克)
 
    :param height: 身高(米)
 
    :return: BMI 值
 
    """
 
    return weight / (height ** 2)
 
# calculate("abc", 1.75)  # IDE 会警告,但运行时不会报错
 

7. 文件操作

7.1 文件打开模式

模式说明文件不存在
r只读(默认)报错
w写入(覆盖)创建新文件
a追加(在末尾添加)创建新文件
r+读写报错
b二进制模式配合以上使用(如 rb, wb

7.2 读文件

 
# 方式一:read() - 一次读取全部
 
with open("data.txt", "r", encoding="utf-8") as f:
 
    content = f.read()
 
    print(content)
 
# 方式二:readline() - 逐行读取
 
with open("data.txt", "r", encoding="utf-8") as f:
 
    while True:
 
        line = f.readline()
 
        if not line:        # 读到文件末尾返回空字符串
 
            break
 
        print(line.strip())  # strip() 去掉行尾换行符
 
# 方式三:readlines() - 读取所有行到列表
 
with open("data.txt", "r", encoding="utf-8") as f:
 
    lines = f.readlines()
 
    for line in lines:
 
        print(line.strip())
 
# 方式四:直接遍历文件对象(推荐!内存友好)
 
with open("data.txt", "r", encoding="utf-8") as f:
 
    for line in f:             # 文件对象本身是可迭代的
 
        print(line.strip())
 

7.3 写文件

 
# write() - 写入字符串
 
with open("output.txt", "w", encoding="utf-8") as f:
 
    f.write("第一行\n")
 
    f.write("第二行\n")
 
# writelines() - 写入列表
 
lines = ["第一行\n", "第二行\n", "第三行\n"]
 
with open("output.txt", "w", encoding="utf-8") as f:
 
    f.writelines(lines)
 

7.4 with 语句详解

 
# ❌ 传统方式:需要手动关闭
 
f = open("data.txt", "r", encoding="utf-8")
 
try:
 
    content = f.read()
 
finally:
 
    f.close()        # 必须手动关闭,否则资源泄漏
 
# ✅ with 方式:自动关闭
 
with open("data.txt", "r", encoding="utf-8") as f:
 
    content = f.read()
 
# 离开 with 块后,文件自动关闭(即使发生异常)
 

📝 综合练习:日记本程序

 
"""
 
简易日记本
 
"""
 
import os
 
from datetime import datetime
 
DIARY_FILE = "diary.txt"
 
def add_entry():
 
    """添加一篇日记"""
 
    content = input("今天想写点什么?\n> ")
 
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 
    with open(DIARY_FILE, "a", encoding="utf-8") as f:
 
        f.write(f"\n{'='*40}\n")
 
        f.write(f"[{timestamp}]\n")
 
        f.write(f"{content}\n")
 
def view_entries():
 
    """查看所有日记"""
 
    if not os.path.exists(DIARY_FILE):
 
        print("还没有写过日记")
 
        return
 
    with open(DIARY_FILE, "r", encoding="utf-8") as f:
 
        for line in f:
 
            print(line, end="")
 
def search_entries(keyword):
 
    """搜索包含关键字的日记"""
 
    if not os.path.exists(DIARY_FILE):
 
        print("还没有写过日记")
 
        return
 
    with open(DIARY_FILE, "r", encoding="utf-8") as f:
 
        content = f.read()
 
    entries = content.split("=" * 40)
 
    found = [e for e in entries if keyword in e]
 
    if found:
 
        for entry in found:
 
            print(entry)
 
    else:
 
        print(f"未找到包含 '{keyword}' 的日记")
 
# 主菜单(lambda 做命令分发)
 
commands = {
 
    "1": lambda: add_entry(),
 
    "2": lambda: view_entries(),
 
    "3": lambda: search_entries(input("搜索关键字:")),
 
    "4": lambda: exit("再见!")
 
}
 
while True:
 
    print("\n=== 日记本 ===")
 
    print("1. 写日记  2. 查看全部  3. 搜索  4. 退出")
 
    choice = input("> ")
 
    action = commands.get(choice)
 
    if action:
 
        action()
 
    else:
 
        print("无效选择")
 

速记卡(面试闪卡)

Q1:一句话讲清「Day 07:函数进阶与文件操作」到底是什么?

A:函数值/作用域/闭包/递归与文件 IO。

Q2:作用域 LEGB:变量按层往外找 —— 怎么理解?

A:像在公司找人:先自己工位(Local)找不到→问隔壁组(Enclosing)→问公司前台(Global)→最后查通讯录(Built-in)。Python 按 LEGB 顺序找变量;函数内改全局得 global、改外层得 nonlocal,否则 UnboundLocalError。

Q3:闭包:函数记住外层变量 —— 怎么理解?

A:像外卖小哥记住你的门牌号:make_power(2) 返回的函数把 exponent=2 带在身上,以后 square(5) 自动用 2 次方。本质是 Closure(闭包)= 函数 + 它捕获的外部变量,常用于数据隐藏(私有计数器)。

Q4:递归:函数自己调自己 —— 怎么理解?

A:像俄罗斯套娃:factorial(n) 调 factorial(n-1),每次问题缩小一圈,撞到终止条件(n<=1)才回头乘回去。本质是 Recursion(递归),三要素:终止条件、问题递减、同类子问题;太深会 RecursionError(超 1000 层)。

Q5:文件操作:with 自动关文件 —— 怎么理解?

A:像借书登记:手动 open 不还(没 close)会资源泄漏;with open(...) as f 像自动还书机,离开作用域文件自动关闭,哪怕报错也关。读用 read/readline/遍历文件对象(最省内存),写用 write/writelines。

Q6:核心速记主线有哪些?

  • LEGB 四层作用域查找

  • 闭包=函数+捕获变量

  • 递归三要素防无限

  • with 自动关文件防泄漏

口诀

A:作用域 LEGB,由内向外找

闭包记外变量,函数带私镖

递归自己调自己,终止条件不能少

with 开文件自动关,忘记 close 会漏掉

相关链接


技术学习路线图 > 函数与 OOP