Day8:Python高级特性实战——列表推导+生成器+装饰器+上下文管理器(Day8完整笔记+踩坑记录)
这是 Python 熟练度特训的第 8 天。前 7 天我们从猜数字一路干到了异步编程,今天开始接触真正"高级"的东西。
四个特性:列表推导、生成器、装饰器、上下文管理器。它们不改变 Python 的语法,但能改变你写代码的方式。
今天的综合项目:日志分析器——用这四个特性分析服务器日志。
目录
一、四个特性一句话速览
| 特性 | 一句话 | 解决了什么问题 |
|---|---|---|
| 列表推导 | [表达式 for 变量 in 列表 if 条件] |
一行代替 for 循环 + if 判断 + append |
| 生成器 | 用 yield 代替 return,用的时候才算 |
处理大文件不占内存 |
| 装饰器 | @timer 给函数加功能,不改原函数代码 |
给函数统一加计时/日志/权限 |
| 上下文管理器 | with open(...) as f: |
确保资源用完自动关闭 |
二、列表推导式
最简模板
python
[表达式 for 变量 in 可迭代对象 if 条件]
三个级别(从小到大)
python
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 级别 1:基础变换
[n * 2 for n in nums] # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# 级别 2:只过滤不变换
[n for n in nums if n > 5] # [6, 7, 8, 9, 10]
# 级别 3:过滤 + 变换
[n * 2 for n in nums if n % 2 == 0] # [4, 8, 12, 16, 20]
等价写法对照
python
# 普通写法(4行)
result = []
for n in nums:
if n > 5:
result.append(n)
# 列表推导(1行)
result = [n for n in nums if n > 5]
💡 执行顺序是:先 for → 再 if → 最后算表达式。不是从左到右读!
练习
python
scores = [45, 78, 92, 56, 88, 34, 71]
pass_scores = [s for s in scores if s >= 60]
print(pass_scores) # [78, 92, 88, 71]
words = ["hello", "world", "python", "ai", "code"]
upper_words = [w.upper() for w in words]
print(upper_words) # ['HELLO', 'WORLD', 'PYTHON', 'AI', 'CODE']
nums = [x for x in range(1, 21) if x % 3 == 0]
print(nums) # [3, 6, 9, 12, 15, 18]
三、生成器
yield vs return
python
# return:函数结束,下次重新开始
def normal():
return 1
return 2 # 永远不会执行
# yield:函数暂停,下次从暂停处继续
def gen():
yield 1 # 暂停在这里
yield 2 # 下次从这里继续
return |
yield |
|
|---|---|---|
| 行为 | 函数结束 | 函数暂停,下次继续 |
| 状态 | 不保留 | 保留局部变量 |
| 返回值次数 | 1 次 | 可以多次 |
| 内存 | 一次性全算完 | 用的时候才算 |
最简模板:逐行读文件
python
def read_lines(file_path="test.log"):
"""生成器:逐行读取文件"""
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
yield line
# 方式 1:for 循环自动取完
for line in read_lines():
print(line)
# 方式 2:手动 next() 一次取一行
g = read_lines()
print(next(g)) # 第 1 行
print(next(g)) # 第 2 行
生成器 vs 列表推导(语法区别)
python
list_comp = [x * x for x in range(10)] # [] → 列表,一次性算完
gen_expr = (x * x for x in range(10)) # () → 生成器,用的时候才算
type(list_comp) # <class 'list'>
type(gen_expr) # <class 'generator'>
len(list_comp) # ✅ 10
len(gen_expr) # ❌ TypeError!生成器没有长度
💡 生成器不存数据,所以没有 len()。它只是一个"生产数据的配方"。
扩展:斐波那契生成器
python
def fibonacci(n, a=0, b=1):
count = 0
while count < n:
yield a
a, b = b, a + b
count += 1
for num in fibonacci(10):
print(num, end=" ") # 0 1 1 2 3 5 8 13 21 34
四、装饰器
核心思想
在不改原函数代码的前提下,给函数加额外功能。
最简模板
python
def timer(func): # 1. 接收一个函数
def wrapper(*args, **kwargs): # 2. 定义包装函数
start = time.time() # 3. 执行前做的事
result = func(*args, **kwargs) # 4. 执行原函数
print(f"耗时:{time.time()-start:.4f}秒") # 5. 执行后做的事
return result # 6. 返回原函数的结果
return wrapper # 7. 返回包装函数(不执行!)
@timer 干了什么?
python
@timer
def slow_function():
...
# 等价于:
slow_function = timer(slow_function)
# timer 接收 slow_function,返回 wrapper
# 以后调 slow_function(),实际调的是 wrapper()
装饰器三步套路
第 1 步:外层函数(接收原函数)
↓
第 2 步:内层函数(加额外功能 + 调用原函数)
↓
第 3 步:返回内层函数(不执行)
练习:counter 装饰器
python
def counter(func):
count = 0
def wrapper(*args, **kwargs):
nonlocal count
count += 1
print(f"{func.__name__} 被调用了 {count} 次")
return func(*args, **kwargs)
return wrapper
@counter
def say_hello():
print("你好!")
say_hello() # say_hello 被调用了 1 次 / 你好!
say_hello() # say_hello 被调用了 2 次 / 你好!
💡 nonlocal 关键字用来修改外层函数的局部变量(这里是 count)。
五、上下文管理器
你早就用过了
python
with open("file.txt", "r", encoding="utf-8") as f:
data = f.read()
# 到这里文件自动关闭
为什么要有 with?
python
# 没有 with:手动关,容易忘
f = open("file.txt")
data = f.read()
f.close() # ← 忘了就内存泄漏
# 有 with:自动关
with open("file.txt") as f:
data = f.read()
# 到这里自动 close(),即使报错也会执行
自己写一个
python
import time
class Timer:
def __init__(self, name):
self.name = name
def __enter__(self): # with 开始时执行
print(f"开始:{self.name}")
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb): # with 结束时执行
print(f"{self.name} 耗时:{time.time() - self.start:.4f}秒")
return False
# 使用
with Timer("计算"):
total = sum(range(1000000))
# 输出:
# 开始:计算
# 计算 耗时:0.02秒
💡 __exit__ 的 return False 表示"不吞掉异常"。如果发生了错误,程序会正常报错。返回 True 则会吞掉异常(很少用)。
六、综合项目:日志分析器
把今天学的四个特性全都用在一个项目上。分析一份 Nginx 风格的日志文件,找出所有 5xx 错误请求。
模拟日志文件(access.log)
192.168.1.1 - - [04/Jun/2026:10:15:30 +0800] "GET /index.html HTTP/1.1" 200 2326
10.0.0.2 - - [04/Jun/2026:10:16:45 +0800] "POST /api/login HTTP/1.1" 401 128
172.16.0.1 - - [04/Jun/2026:10:18:12 +0800] "GET /api/users HTTP/1.1" 500 52
...
每行格式:IP - - [时间] "请求方式 路径 协议" 状态码 大小 状态码:200=正常、404=找不到、500=服务器错误、403=无权限……
完整代码
python
import time
def timer(func): # 装饰器
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} 耗时:{time.time() - start:.4f}秒")
return result
return wrapper
def read_lines(file_path="access.log"): # 生成器
with open(file_path, "r", encoding="utf-8") as f: # 上下文管理器
for line in f:
yield line
@timer
def filter_errors(file_path="access.log"):
lines = list(read_lines(file_path))
errors = [line for line in lines if '" 5' in line] # 列表推导
print(f"共 {len(errors)} 条错误:")
for e in errors:
print(e.strip())
return errors
filter_errors()
四个特性的分工
日志文件 (access.log)
│
▼
[生成器 read_lines] → 逐行 yield,不把整个文件读进内存
│
▼
[列表推导] → [line for line in lines if '" 5' in line]
一行代替 for + if + append
│
▼
[装饰器 @timer] → 自动计时,不改 filter_errors 的代码
│
▼
[上下文管理器] → with open() 自动关文件
七、你踩过的坑 & 你问过的问题
坑 1:** 不是合法的单目运算符
❌ x** # 语法错误
✅ x * x # 平方
✅ x ** 2 # 平方
把 C++ 的 **(指针的指针)和 Python 的 **(幂运算符)记混了。正常,两个语言同一个符号含义不同。
坑 2:print(生成器对象()) 不会打印数据
python
print(read_lines())
# 输出:<generator object read_lines at 0x00000257FB8F1D50>
生成器对象 = 订单号,不是外卖本身。想拿数据要用 for 或 next()。
坑 3:过滤条件 " 5" 不够精确
第一次用 " 5" 过滤,结果日志时间 10:16:**45**、10:19:**15** 也匹配到了,混入一条状态码 200 的正常请求。
python
# ❌ 匹配到时间里的 15/45
errors = [line for line in lines if " 5" in line]
# ✅ 只匹配状态码(" 500"、" 502"),状态码前一个字符是双引号
errors = [line for line in lines if '" 5' in line]
坑 4:函数名拼写错误
python
def filter_errors(...): # 写成了 filler_errose
...
跑完输出 "filler_errose 耗时:..." 才发现。
坑 5:上下文管理器的属性命名混乱
python
class Timer:
def __init__(self, name):
self.func = name # ❌ func 存的是字符串,容易误导
self.name = name # ✅ 见名知意
命名原则:变量名要准确反映它存的是什么。
你问过的好问题
Q1:斐波那契生成器的 a, b 能不能改成传参? → 可以!加默认参数:def fibonacci(n, a=0, b=1):,既保留默认行为又支持自定义。
Q2:列表推导里的 x * x 是什么? → x 的平方。也可以写成 x ** 2。
Q3:time 是数据库吗? → 不是。import time 是 Python 内置的时间模块。time.time() 返回当前时间戳秒数。
八、需要背下来的模板
这些模板每次写代码都用得上。复制到你的笔记里,每天看一遍直到能默写。
装饰器模板
python
def my_decorator(func):
def wrapper(*args, **kwargs):
# 执行前做的事
result = func(*args, **kwargs)
# 执行后做的事
return result
return wrapper
生成器模板
python
def my_generator(path):
with open(path, "r", encoding="utf-8") as f:
for line in f:
yield line
列表推导模板
python
[表达式 for 变量 in 可迭代对象 if 条件]
上下文管理器模板
python
class MyManager:
def __init__(self, ...):
...
def __enter__(self):
# with 开始时
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# with 结束时(即使报错也会执行)
return False
结尾
今天四个特性虽然看起来是用在"小地方",但它们是 Python 工程化的基本功。装饰器在后面的 API 调用中用来加超时/重试,生成器在数据处理中用来读大文件省内存,列表推导在任何地方都能让你的代码更简洁。
记住核心原则:不要背语法,背模板。语法可能忘,模板不会。
更多推荐


所有评论(0)