Python3 进阶到底该怎么练:三个实战示例带你写

编程狮(w3cschool.cn) 2026-09-04 11:10:57 浏览数 (26)
反馈

你现在最该做的不是再刷一遍语法,而是把三个工具练成肌肉记忆:列表推导式、装饰器、上下文管理器。Python3 进阶的分水岭从来不是认识多少关键字,而是能不能用语言自带的机制把重复代码消灭掉。同样一个需求,初学者写十五行嵌套循环加一堆临时变量,熟手三行就写完而且更快;同样一个日志需求,初学者在每个函数里手写 print,熟手写一个装饰器套上去就完事。编程狮的课程体系里把这三样归为“可复用能力”,因为它们不依赖任何框架,写脚本、做爬虫、开发接口全都用得上。这篇笔记给出三段可以直接运行的代码,每段都对比“改写前”和“改写后”,并说清适用边界和常见坑,看完你就知道 Python3 进阶该练什么。

Python3 进阶三个实战示例封面

一、从“会写”到“写好”差在哪

先做个自我诊断。如果你已经掌握变量、循环、函数和类,却总觉得自己的代码“能跑但不够漂亮”,那么卡点大概率在三个地方:重复的循环骨架、散落各处的横切逻辑、以及手工管理的资源开关。Python3 进阶要解决的正是这三类问题,而不是去学更多冷门语法。

第一类是循环骨架重复。“建一个空列表、for 遍历、if 过滤、append 进去”这四步在项目里会出现几十次,每次都要写四行,还容易把变量名写串。第二类是横切逻辑散落,比如打日志、计时、重试、权限校验,这些跟业务无关但每个函数都要来一遍,复制粘贴的代价是改一处要改二十处。第三类是资源管理,文件、数据库连接、锁、线程池都需要“用完必须释放”,一旦中间抛出异常就可能泄漏。

Python3 进阶的核心思路是把这三类共性抽出来交给语言机制处理:推导式解决第一类,装饰器解决第二类,上下文管理器解决第三类。它们的共同点是都属于标准语法,从 Python 3.0 起就存在,不需要引入任何依赖,也不会让代码变得晦涩难懂。

需要提醒一句,进阶不等于炫技。判断一段改写是否成功的标准只有两个:行数是不是变少了、意图是不是更清楚了。如果为了少写两行而搞出一个三层嵌套还带条件表达式的推导式,那是退步不是进阶。带着这个标准,我们看三段具体代码。

二、用列表推导式替代循环

先看最容易上手的一个。需求很常见:从一批订单金额里挑出大于一百的,并统一乘以折扣系数。改写前是标准四步骨架:

prices = [58, 120, 99, 340, 210]


result = []
for p in prices:
    if p > 100:
        result.append(round(p * 0.85, 2))
print(result)
# 输出: [102.0, 289.0, 178.5]


# 改写后:一行推导式,语义完全等价
result = [round(p * 0.85, 2) for p in prices if p > 100]
print(result)
# 输出: [102.0, 289.0, 178.5]

推导式的读法是固定的:先看 for 部分确定遍历源,再看 if 部分确定过滤条件,最后看最前面的表达式确定每个元素怎么变形。顺序上“筛完再变形”,所以昂贵的计算不会浪费在被过滤掉的元素上。除了列表,同样的写法还有字典推导式 {k: v for k, v in items} 和集合推导式,把方括号换成花括号即可。

性能上推导式确实更快,因为它把 append 的方法查找和调用放进了解释器内部循环,实测在十万级数据上通常比手写 for 快百分之二三十。想连带把生成器表达式一起弄懂,可以对照Python3 教程里的推导式章节,把圆括号版本 (x for x in data) 也练一遍,它按需产出元素、内存占用恒定,处理大文件时比列表更合适。

边界也要记清楚。第一,推导式最多嵌两层,超过两层就该拆成函数,可读性优先于行数。第二,推导式里不要写有副作用的代码,比如一边推导一边写数据库,那种场景老老实实用 for 循环。第三,需要中途 break 的逻辑推导式做不到,别硬凑。这是 Python3 进阶里最常见的过度使用点。

三、用装饰器统一加日志

第二个工具解决横切逻辑。假设产品要求所有关键函数都记录耗时,改写前你得在每个函数首尾插入时间戳,几十个函数就是几十份重复代码。装饰器的做法是把这段逻辑写一次,然后用 @ 语法贴到任意函数上:

import functools
import time


def timed(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        try:
            return func(*args, **kwargs)
        finally:
            cost = (time.perf_counter() - start) * 1000
            print(f"[LOG] {func.__name__} 耗时 {cost:.2f} ms")
    return wrapper


@timed
def fetch_orders(page):
    time.sleep(0.05)
    return [f"order-{page}-{i}" for i in range(3)]


print(fetch_orders(1))
# 输出: [LOG] fetch_orders 耗时 50.31 ms
# 输出: ['order-1-0', 'order-1-1', 'order-1-2']

三个细节决定这段代码是否合格。第一,*args**kwargs 必须写全,否则被装饰的函数一改签名就报错。第二,functools.wraps 不能省,它把原函数的名字、文档字符串、注解复制到 wrapper 上;漏了它,fetch_orders.__name__ 会变成 wrapper,日志和调试信息全部失真,很多框架的路由注册也会因此失效。第三,计时和日志放在 finally 里,这样即使业务函数抛异常也能记录到耗时,不会因为出错就丢掉观测数据。

理解装饰器的本质有助于举一反三:@timed 只是 fetch_orders = timed(fetch_orders) 的语法糖,装饰器就是“接收函数、返回新函数”的普通函数。想加参数就再包一层,写成 @retry(times=3) 这种形式;想给类方法用同样可以,注意第一个参数是 self 就行。Python3 进阶阶段掌握它,等于拿到了重试、缓存、限流、鉴权这一整类需求的通用解法,标准库的 functools.lru_cache 本身就是个现成的装饰器。

四、用上下文管理器管理资源

第三个工具解决“用完必须释放”。你熟悉的 with open(...) 就是上下文管理器,它保证文件无论正常结束还是抛异常都会被关闭。自己实现一个最省事的办法是用 contextlib:

import sqlite3
from contextlib import contextmanager


@contextmanager
def get_conn(db_path):
    conn = sqlite3.connect(db_path)
    print("连接已建立")
    try:
        yield conn
        conn.commit()
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()
        print("连接已关闭")


with get_conn(":memory:") as conn:
    conn.execute("CREATE TABLE t (id INTEGER, name TEXT)")
    conn.execute("INSERT INTO t VALUES (1, 'w3cschool')")
    print(conn.execute("SELECT name FROM t").fetchall())
# 输出: 连接已建立
# 输出: [('w3cschool',)]
# 输出: 连接已关闭

yield 之前的部分相当于进入时执行的准备动作,yield 交出去的对象就是 as 后面拿到的变量,yield 之后的部分是退出时的收尾。加上 try 与 finally 之后,异常路径会先回滚再关闭连接,正常路径则提交后关闭,调用方一行 with 就把事务和释放全包了。如果你更习惯面向对象写法,也可以定义一个类并实现 __enter____exit__ 两个方法,效果等价,contextlib 只是省掉了模板代码。

这段代码可以直接复制到Python3 在线运行里跑,用内存数据库不需要任何本地环境。想验证异常路径,在 with 块里故意写一句错误 SQL,你会看到“连接已关闭”依然被打印出来——这正是上下文管理器的价值。顺带提一个实用点:contextlib.suppress 可以优雅地忽略指定异常,比空的 except 块清晰得多。真实项目里,临时切换工作目录、临时改环境变量、给代码块计时,都很适合封装成上下文管理器。

五、三个技巧怎么组合

单独用已经有价值,组合起来威力更大。举个贴近实战的例子:写一个数据导出脚本,外层用上下文管理器拿数据库连接并保证释放,函数上贴计时装饰器观测每步耗时,函数内部用推导式做数据清洗和字段映射。三者各管一层——资源、横切、数据变形,互不干扰,代码读起来像一份说明书。

组合时有个先后顺序建议:先用推导式把数据处理写清楚,因为它离业务最近;再用上下文管理器把资源边界圈出来,避免泄漏;最后才考虑加装饰器,因为它是观测和增强手段,属于锦上添花。反过来先写装饰器容易过度设计,把简单脚本搞成框架。

给你一条可落地的练习路线:找一个自己写过的一百行以内的旧脚本,按三步改写。第一步把所有“空列表加 for 加 append”的段落改成推导式,跑一遍确认输出一致。第二步把每个 open 和数据库连接都换成 with 写法。第三步挑两个最关键的函数加上计时装饰器,看看瓶颈在哪。改完对比行数和可读性,你会直观感受到 Python3 进阶带来的差别。如果想有系统的题目练手,编程狮站内的 python3教程 与配套练习可以顺着章节刷。

要提醒的是,进阶是渐进的过程,不要指望一次改写就彻底掌握。真正的判断标准是当你下次遇到同类需求时,脑子里第一反应就是这三个工具中的某一个,而不是先想“再写个循环”。到那一步,Python3 进阶这件事就算落地了。

Python3 三个进阶技巧怎么组合

总结

Python3 进阶的关键不是背更多语法,而是把三个语言机制用熟:列表推导式把“空列表加循环加过滤加追加”四步压成一行,同时更快也更好读,但嵌套不要超过两层、不要带副作用;装饰器把日志、计时、重试、缓存这类横切逻辑写一次到处复用,务必带上 *args**kwargsfunctools.wraps,收尾动作放进 finally;上下文管理器用 with 圈定资源边界,contextlib 的 @contextmanager 加上 try 与 finally 就能同时处理提交、回滚与关闭。三者分别管数据变形、横切增强、资源生命周期,组合使用时按“数据、资源、观测”的顺序落地。拿一个旧脚本按这三步改写一遍,比再看十篇文章都有效。

延伸学习

常见问题

Q:列表推导式一定比 for 循环快吗?

A:结论:多数情况更快,但差距只在数据量大时明显。
推导式把元素追加动作下沉到解释器内部,省掉了每轮的方法查找与函数调用开销,十万级数据上通常快百分之二三十。
若循环体内部逻辑很重,瓶颈就不在追加动作上,此时改写只能提升可读性,不要为性能而强行压成一行。

Q:装饰器为什么必须写 functools.wraps?

A:结论:不写会丢掉原函数的元信息。
装饰后返回的是内层 wrapper 函数,__name____doc__、类型注解都会变成 wrapper 的,日志打印和调试栈会指向错误名字。
更严重的是不少 Web 框架靠函数名注册路由,元信息失真会直接导致路由冲突或注册失败。

Q:上下文管理器和 try/finally 有什么区别?

A:结论:效果等价,但复用性完全不同。
try 与 finally 是一次性的,每个使用点都要重写一遍收尾代码;上下文管理器把这段逻辑封装成可复用对象,调用方只写一行 with。
需要在多处保证同一套释放逻辑时,一定优先封装上下文管理器。

Q:Python3 进阶应该先学哪个?

A:结论:按列表推导式、上下文管理器、装饰器的顺序学。
推导式改造成本最低、见效最快,适合建立信心;上下文管理器概念清晰且能立刻避免资源泄漏。
装饰器涉及函数是一等对象、闭包、语法糖等概念,理解门槛最高,放在最后攻克更顺畅。

0 人点赞