当前位置:首页>python>Python教程 — 装饰器、生成器与文件IO

Python教程 — 装饰器、生成器与文件IO

  • 2026-10-11 05:42:56
Python教程 — 装饰器、生成器与文件IO

> 上节课搞定了面向对象,这节课我们深入 Python 的几个杀手级特性:装饰器、生成器、文件操作和异常处理。这四个概念掌握了,你的 Python 水平就能从"入门"升级到"能用"了。

---

## 一、装饰器(Decorator)—— 函数的"外衣"

### 1.1 基本概念

```python

import time

# 不带参数的装饰器:计算函数运行时间

deftimer(func):

"""装饰器:测量函数执行时间"""

defwrapper(*args, **kwargs):

        start = time.time()

        result = func(*args, **kwargs)

        end = time.time()

print(f"{func.__name__} 执行耗时: {end - start:.4f}秒")

return result

return wrapper

@timer

defslow_function():

"""模拟一个慢函数"""

    time.sleep(1)

return"完成!"

slow_function()

# 输出:slow_function 执行耗时: 1.0012秒

```

### 1.2 带参数的装饰器

```python

import functools

defrepeat(n_times):

"""带参数的装饰器:重复执行函数"""

defdecorator(func):

@functools.wraps(func)  # 保留原函数的元信息

defwrapper(*args, **kwargs):

for _ inrange(n_times):

                result = func(*args, **kwargs)

return result

return wrapper

return decorator

@repeat(n_times=3)

defgreet(name):

print(f"Hello, {name}!")

greet("Alice")

# 输出三次:Hello, Alice!

```

### 1.3 装饰器的常见应用场景

```python

import functools

# ====== 1. 缓存装饰器(记忆化)======

defmemoize(func):

"""缓存函数结果,避免重复计算"""

    cache = {}

@functools.wraps(func)

defwrapper(*args):

if args notin cache:

            cache[args] = func(*args)

return cache[args]

return wrapper

@memoize

deffibonacci(n):

if n < 2:

return n

return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(50))  # 非常快!(缓存了中间结果)

# ====== 2. 权限校验装饰器 ======

defrequire_role(role):

"""检查用户角色"""

defdecorator(func):

@functools.wraps(func)

defwrapper(user, *args, **kwargs):

if user.get("role") == role or user.get("role") == "admin":

return func(user, *args, **kwargs)

else:

raisePermissionError(f"需要 {role} 权限!")

return wrapper

return decorator

# 模拟用户

admin_user = {"name": "张三", "role": "admin"}

guest_user = {"name": "李四", "role": "guest"}

@require_role("admin")

defdelete_database(user):

print(f"✅ {user['name']} 执行了删除数据库操作")

delete_database(admin_user)       # ✅ 通过

# delete_database(guest_user)    # ❌ PermissionError

```

### 1.4 多个装饰器叠加

```python

deflogging_decorator(func):

@functools.wraps(func)

defwrapper(*args, **kwargs):

print(f"[LOG] 调用 {func.__name__}")

        result = func(*args, **kwargs)

print(f"[LOG] {func.__name__} 返回: {result}")

return result

return wrapper

defvalidate_input(func):

@functools.wraps(func)

defwrapper(*args, **kwargs):

for arg in args:

ifisinstance(arg, str) andnot arg:

raiseValueError("不允许空字符串参数")

return func(*args, **kwargs)

return wrapper

@logging_decorator

@validate_input

defdivide(a, b):

return a / b

print(divide(10, 2))  

# [LOG] 调用 divide

# [LOG] divide 返回: 5.0

```

---

## 二、生成器(Generator)—— 省内存的数据流

### 2.1 yield 关键字

```python

# 普通函数 vs 生成器函数

# 普通函数:一次性返回整个列表

defget_evens_normal(n):

    evens = []

for i inrange(n):

if i % 2 == 0:

            evens.append(i)

return evens  # 全部放在内存里

# 生成器函数:逐个产出数据

defget_evens_gen(n):

for i inrange(n):

if i % 2 == 0:

yield i  # 每次产出一个值,暂停在这里

# 使用

print(list(get_evens_normal(10)))   # [0, 2, 4, 6, 8]

print(list(get_evens_gen(10)))       # [0, 2, 4, 6, 8]

```

>**关键区别**:

>- 普通函数用 `return`,一次返回所有结果

>- 生成器用 `yield`,惰性求值,需要时才计算

>- 对于大数���据集(百万级),生成器节省大量内存

### 2.2 生成器表达式

```python

# 类似列表推导式,但是用圆括号

squares = (x**2for x inrange(1000000))  # 几乎不占内存!

# 而不是

squares = [x**2for x inrange(1000000)]   # 占用几百MB

# 逐个消费生成器

print(next(squares))  # 0

print(next(squares))  # 1

print(next(squares))  # 4

# 配合 sum() 等函数使用

total = sum(x**2for x inrange(1000000))  # 高效!

```

### 2.3 实用的生成器示例

```python

import itertools

# 无限生成器

defcountdown(n):

"""倒计时"""

while n > 0:

yield n

        n -= 1

defnaturals():

"""自然数序列(无限)"""

    n = 1

whileTrue:

yield n

        n += 1

# 从 naturals 中取前 10 个

for i in itertools.islice(naturals(), 10):

print(i, end=" ")  # 1 2 3 4 5 6 7 8 9 10

# 数据流水线:像管道一样加工数据

defread_lines(filename):

"""逐行读取文件"""

withopen(filename, "r", encoding="utf-8") as f:

for line in f:

yield line.strip()

deffilter_non_empty(lines):

"""过滤空行"""

for line in lines:

if line:

yield line

defnormalize(lines):

"""统一转换为小写"""

for line in lines:

yield line.lower()

# 串联使用

# lines = normalize(filter_non_empty(read_lines("data.txt")))

# for line in lines:

#     print(line)

```

### 2.4 生成器发送值

```python

defaccumulator():

"""累加器:接收值并产出累计和"""

    total = 0

whileTrue:

        value = yield total

if value isNone:

break

        total += value

acc = accumulator()

next(acc)  # 启动生成器(预读)

print(acc.send(10))  # 10

print(acc.send(20))  # 30

print(acc.send(15))  # 45

acc.close()

```

---

## 三、文件操作(File IO)

### 3.1 文本文件读写

```python

# ====== 写入文件 ======

text = """

Python 是一门优秀的编程语言。

它的语法简洁,适合初学者入门。

2026 年 Python 依然是最热门的语言之一。

"""

withopen("demo.txt", "w", encoding="utf-8") as f:

    f.write(text)

print("✅ 文件已写入")

# ====== 读取文件 ======

withopen("demo.txt", "r", encoding="utf-8") as f:

    content = f.read()

print(content)

# ====== 逐行读取 ======

withopen("demo.txt", "r", encoding="utf-8") as f:

for line in f:

print(line.strip())

# ====== 读取所有行到列表 ======

withopen("demo.txt", "r", encoding="utf-8") as f:

    lines = f.readlines()

print(f"共 {len(lines)} 行")

```

>**关键**:始终使用 `with` 语句!它会自动关闭文件,即使发生异常。

### 3.2 文件操作模式

| 模式 | 说明 |

|------|------|

| `r` | 只读(默认),文件不存在时报错 |

| `w` | 只写,文件不存在则创建,存在则清空 |

| `a` | 追加,文件不存在则创建 |

| `r+` | 读写 |

| `rb` / `wb` | 二进制模式(用于图片等) |

### 3.3 CSV 文件处理

```python

import csv

# 写入 CSV

data = [

    ["姓名", "年龄", "城市"],

    ["张三", 25, "北京"],

    ["李四", 30, "上海"],

    ["王五", 28, "广州"],

]

withopen("people.csv", "w", encoding="utf-8", newline="") as f:

    writer = csv.writer(f)

    writer.writerows(data)

# 读取 CSV

withopen("people.csv", "r", encoding="utf-8") as f:

    reader = csv.reader(f)

    header = next(reader)  # 跳过表头

for row in reader:

print(f"{row[0]}, {row[1]}岁, 来自{row[2]}")

# 使用 DictReader(更友好)

withopen("people.csv", "r", encoding="utf-8") as f:

    reader = csv.DictReader(f)

for row in reader:

print(f"{row['姓名']} - {row['年龄']}岁")

```

### 3.4 JSON 文件处理

```python

import json

# 写入 JSON

data = {

"name": "张三",

"age": 25,

"hobbies": ["阅读", "编程", "跑步"],

"address": {"city": "北京", "zip": "100000"}

}

withopen("data.json", "w", encoding="utf-8") as f:

    json.dump(data, f, ensure_ascii=False, indent=2)

# ensure_ascii=False: 中文正常显示

# indent=2: 格式化缩进

# 读取 JSON

withopen("data.json", "r", encoding="utf-8") as f:

    loaded = json.load(f)

print(loaded["name"])        # 张三

print(loaded["address"]["city"])  # 北京

```

---

## 四、异常处理(Exception Handling)

### 4.1 基本结构

```python

try:

# 可能出错的代码

    numerator = int(input("请输入分子: "))

    denominator = int(input("请输入分母: "))

    result = numerator / denominator

print(f"结果: {result}")

exceptValueError:

print("❌ 请输入有效的整数!")

exceptZeroDivisionError:

print("❌ 分母不能为零!")

exceptExceptionas e:

print(f"❌ 未知错误: {e}")

else:

# 没有异常时执行

print("✅ 计算成功!")

finally:

# 无论是否异常都会执行(清理工作)

print("🔄 程序结束")

```

### 4.2 自定义异常

```python

classAgeError(ValueError):

"""年龄错误异常"""

pass

classBankAccount:

def__init__(self, owner, balance):

if balance < 0:

raiseValueError("余额不能为负数")

self.owner = owner

self.balance = balance

defwithdraw(self, amount):

if amount <= 0:

raiseValueError("取款金额必须为正数")

if amount > self.balance:

raise InsufficientFundsError(f"余额不足!需要 ¥{amount},当前 ¥{self.balance}")

self.balance -= amount

return amount

classInsufficientFundsError(Exception):

"""余额不足异常"""

pass

# 使用

try:

    account = BankAccount("张三", 1000)

    account.withdraw(1500)

except InsufficientFundsError as e:

print(f"💸 {e}")

exceptValueErroras e:

print(f"参数错误: {e}")

```

### 4.3 断言(assert)

```python

# 用于开发调试,不符合条件就抛出 AssertionError

defcalculate_average(numbers):

assertlen(numbers) > 0, "列表不能为空"

assertall(isinstance(n, (int, float)) for n in numbers), "所有元素必须是数字"

returnsum(numbers) / len(numbers)

print(calculate_average([85, 90, 92]))  # 89.0

# calculate_average([])  # AssertionError: 列表不能为空

```

---

## 五、综合实战:日志文件分析器

把装饰器、生成器、文件IO、异常处理串在一起:

```python

import json

from datetime import datetime

deftime_it(func):

"""计时装饰器"""

import time

defwrapper(*args, **kwargs):

        start = time.time()

        result = func(*args, **kwargs)

        elapsed = time.time() - start

print(f"[{func.__name__}] 耗时: {elapsed:.3f}秒")

return result

return wrapper

defparse_log_line(line):

"""解析日志行(生成器)"""

    line = line.strip()

ifnot line:

return

try:

        parts = line.split("|")

iflen(parts) != 4:

return

        timestamp = parts[0].strip()

        level = parts[1].strip()

        module = parts[2].strip()

        message = parts[3].strip()

if level notin ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"):

return

yield {

"timestamp": timestamp,

"level": level,

"module": module,

"message": message

        }

exceptException:

return

defgenerate_logs(filename):

"""逐行读取并解析日志(生成器管道)"""

withopen(filename, "r", encoding="utf-8") as f:

for line in f:

            parsed = parse_log_line(line)

if parsed:

yield parsed

@time_it

defanalyze_logs(filename):

"""分析日志文件"""

    logs = list(generate_logs(filename))

# 统计

    levels = {}

    modules = {}

    errors = []

for log in logs:

        level = log["level"]

        module = log["module"]

        levels[level] = levels.get(level, 0) + 1

        modules[module] = modules.get(module, 0) + 1

if level in ("ERROR", "CRITICAL"):

            errors.append(log)

# 输出报告

    report = {

"total": len(logs),

"levels": levels,

"modules": modules,

"error_count": len(errors),

"errors": [

            {"timestamp": e["timestamp"], "message": e["message"]}

for e in errors[:10]  # 最多显示前10条错误

        ]

    }

# 打印摘要

print(f"\n{'='*50}")

print(f"  日志分析报告")

print(f"{'='*50}")

print(f"  总记录数: {report['total']}")

print(f"\n  级别分布:")

for level, count insorted(report['levels'].items()):

        bar = "█" * count // 2

print(f"    {level:8s}{count:4d}{bar}")

print(f"\n  模块分布:")

for module, count insorted(report['modules'].items(), key=lambdax: x[1], reverse=True):

print(f"    {module:15s}{count:4d}")

if errors:

print(f"\n  ⚠️ 错误日志(共 {len(errors)} 条):")

for err in report["errors"]:

print(f"    [{err['timestamp']}] {err['message']}")

print(f"{'='*50}\n")

# 保存报告

withopen("report.json", "w", encoding="utf-8") as f:

        json.dump(report, f, ensure_ascii=False, indent=2)

print("📄 报告已保存到 report.json")

return report

# 创建一个示例日志文件供测试

sample_logs = """

2026-07-02 10:00:01|INFO|app|系统启动完成

2026-07-02 10:00:02|DEBUG|db|连接数据库成功

2026-07-02 10:01:00|INFO|auth|用户登录: admin

2026-07-02 10:02:30|WARNING|cache|缓存命中率下降到 65%

2026-07-02 10:03:00|ERROR|api|请求超时: /api/users

2026-07-02 10:05:00|INFO|app|定时任务执行完成

2026-07-02 10:10:00|ERROR|db|数据库连接断开

2026-07-02 10:10:01|CRITICAL|app|服务降级,切换到备用数据库

2026-07-02 10:15:00|INFO|app|恢复正常运行

2026-07-02 10:20:00|WARNING|api|API 调用频率接近上限

"""

withopen("app.log", "w", encoding="utf-8") as f:

    f.write(sample_logs.strip())

analyze_logs("app.log")

```

运行效果:

```

[analyze_logs] 耗时: 0.002秒

==================================================

  日志分析报告

==================================================

  总记录数: 10

  级别分布:

    DEBUG       1  █

    INFO        5  █████

    WARNING     2  ██

    ERROR       2  ██

    CRITICAL    1  █

  模块分布:

    app             4

    api             2

    db              2

    auth            1

    cache           1

  ⚠️ 错误日志(共 3 条):

    [2026-07-02 10:03:00] 请求超时: /api/users

    [2026-07-02 10:10:00] 数据库连接断开

    [2026-07-02 10:10:01] 服务降级,切换到备用数据库

==================================================

📄 报告已保存到 report.json

```

---

## 六、本节要点总结

| 概念 | 核心要点 |

|------|---------|

| **装饰器** | `@decorator` 语法糖,不修改原函数扩展功能,`@functools.wraps` 保元数据 |

| **生成器** | `yield` 惰性求值,省内存,生成器表达式 `(x**2 for x in range(10))` |

| **文件IO** | 用 `with` 语句自动管理文件、CSV 用 `csv` 模块、JSON 用 `json` 模块 |

| **异常处理** | `try-except-else-finally` 完整结构,自定义异常类 |

---

## 七、练习题

### 练习 1:用装饰器实现重试机制

```python

import functools

import time

defretry(max_retries=3, delay=1):

"""失败重试装饰器"""

defdecorator(func):

@functools.wraps(func)

defwrapper(*args, **kwargs):

for attempt inrange(1, max_retries + 1):

try:

return func(*args, **kwargs)

exceptExceptionas e:

print(f"第 {attempt} 次尝试失败: {e}")

if attempt < max_retries:

                        time.sleep(delay)

else:

raise

return wrapper

return decorator

@retry(max_retries=3, delay=0.5)

defunstable_api():

import random

if random.random() < 0.7:

raiseConnectionError("网络超时")

return"请求成功!"

print(unstable_api())

```

### 练习 2:用生成器实现数据过滤器

```python

defpositive_numbers(nums):

"""只产出正数"""

for n in nums:

if n > 0:

yield n

defsquared(nums):

"""只产出平方"""

for n in nums:

yield n ** 2

deftake(n, iterable):

"""只取前 n 个"""

for i, item inenumerate(iterable):

if i >= n:

return

yield item

# 组合:取前5个正数的平方

result = list(take(5, squared(positive_numbers(range(-10, 20)))))

print(result)  # [1, 4, 9, 16, 25]

```

### 练习 3:JSON 配置文件的读写工具类

### 练习 4:用 try-except 实现输入验证循环

```python

whileTrue:

try:

        age = int(input("请输入年龄: "))

if age < 0:

raiseValueError("年龄不能为负数")

print(f"你输入的年龄是: {age}")

break

exceptValueErroras e:

print(f"❌ 输入无效: {e},请重试")

```

### 练习 5:用 with 语句实现简单的数据库连接池(思考题)

---

## 下期预告

**Python 教程 Episode 05 — 爬虫入门:用 requests + BeautifulSoup 抓网页数据**

- HTTP 基础知识(GET/POST 请求)

- 使用 requests 库发送网络请求

- 解析 HTML:BeautifulSoup 入门

- 反爬策略应对(headers、session、代理)

- 实战:爬取豆瓣电影 Top250

- 进阶:Playwright 抓取动态渲染页面(AJAX/JS 渲染)

---

*如果觉得本系列对你有帮助,欢迎点赞、评论、转发!有任何问题,在留言区讨论。*

最新文章

随机文章