这是 Python 熟练度特训的第 8 天。前 7 天我们从猜数字一路干到了异步编程,今天开始接触真正"高级"的东西。

四个特性:列表推导、生成器、装饰器、上下文管理器。它们不改变 Python 的语法,但能改变你写代码的方式。

今天的综合项目:日志分析器——用这四个特性分析服务器日志。
 

目录

  1. 四个特性一句话速览
  2. 列表推导式
  3. 生成器
  4. 装饰器
  5. 上下文管理器
  6. 综合项目:日志分析器
  7. 你踩过的坑 & 你问过的问题
  8. 需要背下来的模板

一、四个特性一句话速览

特性 一句话 解决了什么问题
列表推导 [表达式 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>

生成器对象 = 订单号,不是外卖本身。想拿数据要用 fornext()

坑 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 调用中用来加超时/重试,生成器在数据处理中用来读大文件省内存,列表推导在任何地方都能让你的代码更简洁。

记住核心原则:不要背语法,背模板。语法可能忘,模板不会。

    更多推荐