ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python进阶语法核心:生成器、装饰器、闭包与上下文管理器实战解析

Python进阶语法核心:生成器、装饰器、闭包与上下文管理器实战解析 Python基础语法练到一定程度很多人会卡在一个尴尬的位置列表推导式会写但看不懂项目里那些带的代码知道有yield这个词但真让自己写个生成器处理大日志还是只会readlines硬啃内存函数传参*args、**kwargs见过无数次但问到闭包到底怎么保留状态支支吾吾说不出所以然。这篇笔记定位很明确默认你已经有Python基础功底变量、循环、函数、类都玩得转想要跨过“能跑”到“写得优雅”这道坎。我整理了Python进阶语法里含金量最高的几个点——迭代器与生成器、装饰器、闭包、上下文管理器从底层执行逻辑讲到真实业务场景最后附上我自己踩过的坑。这些内容不管你是做后端接口、写爬虫脚本还是搞数据处理基本天天碰得到。只要把这篇消化掉再去读开源项目的源码体感会完全不同。1. 进阶之前的思维转变一切皆协议1.1 别再背语法去理解Python的对象协议学Python语法最常犯的错误就是把它当成一个个孤立的知识点去背decorator是一个用法with是一种写法yield是一个关键字。这么记确实快但记完就忘遇到变形题就不会了。进阶的人需要换个视角Python里的所谓“高级语法”底层全是对象协议。什么叫协议就是约定“这个类型的对象应该实现哪些行为方法”。比如你写for x in objPython解释器不会魔法般地遍历obj它做的事是去找obj.__iter__()方法找不到就找obj.__getitem__(0, 1, 2...)都找不到就抛TypeError。with语句也不是语法糖它是去调用对象的__enter__和__exit__。decorator本质上就是一次函数调用func decorator(func)。把这个思路掰过来之后进阶语法就是一盘棋了。你不需要记十几个孤立的所谓“高级特性”只需要记住一条主线每个语法背后都是解释器在按固定步骤调用某些特殊方法。你理解了这一点就能从“语言的使用者”变成“协议的参与者”——不仅能调用现成的迭代器还能自己写一个可迭代对象让自定义的类完美融入for循环。1.2 生活类比协议就是“插座规格”为了把“协议”这个词说透我打个比方。你家里买的任何电器插头规格一定是符合国标的插到墙上插座就能用。Python的协议就是这个“国标规格”——__iter__规定了“你必须是能产出下一个元素的东西”__enter__规定了“你必须是能安全进入和退出的资源”。你写一个类只要把协议方法补齐Python的各种内置语法就自动认你。比如你自己写一个Range类实现了__iter__和__next__它就能被for遍历能被list()转换能被sum()求和。整个Python生态都认这套接口你写的类瞬间拥有了和内置类型一样的“公民待遇”。这就是进阶语法的核心心法不是学一个个孤零零的写法而是学会如何让自己创建的对象符合Python的通行协议。2. 迭代器与生成器从“拿到全部”到“按需产出”2.1 迭代器协议拆解为什么for循环不会卡死很多初学者有个疑惑range(100000000)生成一个上亿的序列为什么程序不直接内存爆炸这就要说到迭代器的价值了。一个对象要成为迭代器核心是实现两个方法__iter__返回迭代器自身和__next__每次调用返回下一个值耗尽时抛出StopIteration。关键在于迭代器不一次性把所有元素放进内存它每次只产出当前需要的那一个。我自己写代码时最常遇到的一个场景是处理几G大小的日志文件。新手做法是content f.read()直接一次性把整个文件读进内存跑一次程序电脑风扇狂转。用迭代器的思路就完全不同——for line in f一行一行读内存占用恒定量级和文件多大没关系。你甚至可以自己写一个斐波那契迭代器感受一下“按需产出”和“一次性算完”的差别class FibIterator: def __init__(self, n): self.n n # 想生成前n个斐波那契数 self.a, self.b 0, 1 self.count 0 def __iter__(self): return self def __next__(self): if self.count self.n: raise StopIteration self.a, self.b self.b, self.a self.b self.count 1 return self.a这个类实例化之后就能被for直接遍历。你观察执行流程会发现每次for循环取下一个值__next__计算一次算完就丢内存里永远只保留两个整数a和b。这就是迭代器协议的全部精髓。2.2 生成器用yield把函数变成迭代器手写一个类实现__iter__和__next__确实有点啰嗦于是Python提供了一个更优雅的写法只要函数里出现yield关键字这个函数就不走普通return逻辑了它会变成一个生成器函数。调用生成器函数不会真正执行函数体而是返回一个生成器对象。真正执行是在for循环推进它的时候每次遇到yield就暂停把值抛出来下次继续从暂停位置往后跑。def fib_generator(n): a, b 0, 1 count 0 while count n: a, b b, a b count 1 yield a这段代码和上面的类写法行为完全一致但代码量少了一半。我自己的经验是凡是需要“按序遍历大数据”或者“不断产生中间值”的场景优先写生成器而不是攒一个list。比较典型的是用生成器表达式做数据管道——把读取、清洗、转换串成一条流水线每个环节都是惰性求值处理千万条数据内存一样稳定。# 数据管道场景读取日志文件逐行清洗再提取关键字 cleaned_lines (line.strip() for line in open(app.log, encodingutf-8) if line.strip()) log_tuples (tuple(line.split(|)) for line in cleaned_lines) error_count sum(1 for item in log_tuples if item[0] ERROR)这里有个细节值得注意生成器表达式能像列表推导式一样写但它外面是圆括号。它在循环和推导式里逐个产出值几乎不占内存。我经常跟人强调如果你写列表推导式只是为了在for循环里遍历一次那就换成生成器表达式不会错。2.3 生成器的底层执行真相暂停与恢复很多教程把yield讲得神乎其神其实你只要做一次实验就懂了在生成器函数里加print观察它什么时候执行。def gen_demo(): print(第一段开始) yield 1 print(第一段结束第二段开始) yield 2 print(全部结束) g gen_demo() print(生成器已创建但函数体尚未执行) print(next(g)) # 打印第一段开始返回1 print(next(g)) # 打印第一段结束第二段开始返回2第一个next(g)调用时“生成器已创建”已经打印了说明构造生成器对象确实没有执行函数体。执行到yield就冻结了当前函数的状态——局部变量、执行到的行号、堆栈信息全部保存。第二个next(g)从上次冻结的地方恢复继续往下跑直到遇见下一个yield或函数结束。理解了这个“暂停和恢复”机制你才能真正体会生成器为什么能处理无限序列。比如你想遍历所有偶数手写while True生成理论上无穷无尽但因为你按需取所以内存和CPU都不会炸。这也是Python协程的雏形函数之间来回切换执行流而这套机制正是asyncio的基础。3. 装饰器给函数穿马甲的高级语法3.1 装饰器的本质就是函数替换装饰器是Python里被“神化”最多的语法其实所谓decorator就是告诉你一件事你定义完函数后解释器自动帮你调一次装饰器函数再用返回值替换掉原来的函数名。def my_decorator(func): def wrapper(*args, **kwargs): print(调用前增强) result func(*args, **kwargs) print(调用后增强) return result return wrapper my_decorator def say_hello(name): return fHello, {name}执行完这段代码后say_hello这个名字指向的不再是你定义的原始函数而是wrapper。你调用say_hello(Tom)实际执行的是wrapper(Tom)wrapper内部再调用原本的函数。一层套一层所以叫“装饰器”一点没错——你给原函数加了装饰但没有改动原函数的代码。这里我跟大家强调一个原则装饰器最适合做横切关注点——那些跟业务逻辑无关但每个函数都要做的公共事务比如打印日志、统计耗时、鉴权校验、重试机制。之所以用装饰器而不是在每个函数里复制粘贴动力就是“单一职责原则”业务函数只关心业务公共逻辑抽出来复用。3.2 装饰器实战一行代码给函数加计时我自己做性能排查时最常用的写法就是计时装饰器代码简单但实用性极高import time import functools def timer_decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): start time.perf_counter() result func(*args, **kwargs) cost (time.perf_counter() - start) * 1000 print(f[计时] {func.__name__} 耗时 {cost:.2f} ms) return result return wrapper timer_decorator def heavy_task(n): total 0 for i in range(n): total i ** 2 return total heavy_task(100000)我特别提醒一下为什么wrapper函数上面还要加functools.wraps(func)因为不加的话装饰后函数的__name__会变成wrapper这会破坏一些依赖函数名的工具比如Flask路由注册有些场景下会有问题调试日志里显示的函数名也全是wrapper。functools.wraps的作用就是把原函数的元信息__name__、__doc__、__module__等拷贝到wrapper上保持“看起来还是原来的函数”。这是装饰器写法里的一个规范动作新手最容易漏。3.3 带参数的装饰器三层嵌套的玩法有时候你希望装饰器能接收参数比如“日志级别”“重试次数”。这时需要再加一层工厂函数def retry(max_attempts3): def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): for attempt in range(1, max_attempts 1): try: return func(*args, **kwargs) except ValueError as e: print(f第 {attempt} 次失败: {e}) raise ValueError(多次重试仍失败) return wrapper return decorator retry(max_attempts5) def unstable_request(): # 模拟可能失败的第三方接口调用 import random if random.random() 0.7: raise ValueError(接口暂时不可用) return ok理解拆解过程retry(max_attempts5)首先执行retry(5)得到decorator然后Python把原函数传给decorator得到wrapper最后用wrapper替换原函数。三层嵌套第一次看确实绕但拆分后就是简单的函数调用。记一个口诀参数给工厂工厂出装饰器装饰器吃函数返回新函数。3.4 装饰器的叠加顺序从下往上执行写Flask或Django的人经常看到多个装饰器叠在一起app.route(/api/user) login_required timer_decorator def get_user(): ...这里有一个非常容易误解的执行顺序。装饰器叠加时先应用离函数定义最近的那个下方再依次向上。也就是timer_decorator先包装原始函数然后login_required包装上一步的结果最后app.route拿到的已经是层层包裹后的函数。调用时则正好相反从上往下执行。我踩过的坑是我把app.route放在最外层自以为“路由装饰器应该优先执行鉴权”结果请求进来直接被timer打点然后才鉴权日志里多了一堆未授权请求的耗时记录。理解这个顺序后你可以设计出清晰的层次最外层做路由映射中间层做权限控制内层做技术埋点。每一层只关心自己的事互不干扰。4. 闭包函数如何记住外层变量4.1 闭包的本质函数环境闭包这个概念在Python面试里出现频率极高但很多人只背定义“内层函数引用了外层函数的变量”。其实闭包的精髓在于理解当外层函数结束后内层函数依然能访问外层函数的局部变量靠的是Python为它创建了一个“闭包环境”。我习惯用一个计数器例子来演示def make_counter(): count 0 def increment(): nonlocal count count 1 return count return increment counter make_counter() print(counter()) # 1 print(counter()) # 2 print(counter()) # 3按普通的函数作用域理解make_counter()执行完count局部变量就该被销毁了。但increment被返回出去而且它内部引用count所以Python把count打包进了increment的闭包环境里让这个变量活了下了来。每次调用counter()操作的还是同一个count于是计数器能持续自增。这里必须说明nonlocal的作用。在嵌套函数里如果只读取外层变量不需要额外声明但如果你想给外层变量重新赋值Python就会认为你在定义一个新局部变量从而与外层变量“失联”。nonlocal就是告诉解释器这个变量不是本层的局部变量去最近的且已绑定的外层作用域找。4.2 闭包的经典坑延迟绑定闭包有一个几乎人人都会踩的坑我在这上面debug过整整一下午。看这段代码def create_multipliers(): multipliers [] for i in range(3): def multiply(x): return x * i multipliers.append(multiply) return multipliers multipliers create_multipliers() for m in multipliers: print(m(2))很多人直觉上认为会输出0、2、4但实际输出是4、4、4。原因是三个multiply函数引用的i是同一个变量。for循环结束后i的值停在2闭包环境里保存的i就是2所以三个函数无论谁调用乘的都固定是2。修法很简单用默认参数把当前i值“钉死”到函数定义那一刻def create_multipliers(): multipliers [] for i in range(3): def multiply(x, ii): return x * i multipliers.append(multiply) return multipliers这个坑的教训值得记一辈子闭包里引用的循环变量最终用到的是循环结束后的值不是定义时的值。如果需要定义时的快照请用默认参数或者再包一层立即执行函数。4.3 闭包与装饰器的不解之缘现在你回头再看装饰器里那个wrapper(*args, **kwargs)函数它在调用func时读取了外层decorator接收到的func参数本质就是一个典型的闭包。所以在实际项目里装饰器就是闭包最广泛的应用场景。更进一步闭包还能用来做“记忆化”缓存函数结果。经典斐波那契数列用递归写会有大量重复计算复杂度指数级套一层闭包做缓存复杂度直接降到线性def memoize(func): cache {} functools.wraps(func) def wrapper(*args): if args not in cache: cache[args] func(*args) return cache[args] return wrapper memoize def fib(n): if n 2: return n return fib(n - 1) fib(n - 2)这里cache也是一个闭包变量被wrapper长期引用所以它的生命周期贯穿了整个程序运行。这种“闭包持有状态 装饰器切入函数执行”的组合拳在实际项目里非常常见你会在缓存中间件、限流器、ORM的懒加载机制里反复见到。5. 上下文管理器用with优雅地管理资源5.1 为什么需要with别忘了清理现场很多人写过这样的代码打开文件、读取、关闭。但问题在于——如果读取过程中抛出异常close()根本执行不到文件句柄就泄漏了。传统修法是try/finally但写起来啰嗦。with语句存在的意义就是让“进入-执行-退出”这三段逻辑固化成一个协议无论中间发生什么退出代码保证执行。# 传统写法 file_obj open(data.txt, encodingutf-8) try: data file_obj.read() finally: file_obj.close() # with 写法 with open(data.txt, encodingutf-8) as f: data f.read()第二条看着顺眼得多而它的底层实现就是两个魔法方法__enter__负责进入返回资源对象__exit__负责退出清理资源。5.2 自己写上下文管理器两种主流姿势第一种是类写法适合逻辑较重、需要封装状态的场景class DatabaseSession: def __enter__(self): self.conn get_connection() return self.conn def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: self.conn.rollback() else: self.conn.commit() self.conn.close() with DatabaseSession() as conn: conn.execute(insert ...)注意__exit__的四个参数exc_type是异常类型exc_val是异常实例exc_tb是traceback对象。如果代码块里没有异常三者都是None。你可以在__exit__里根据有没有异常决定提交还是回滚这就把事务管理收进了with里面。如果__exit__返回True异常会被吞掉返回False默认则继续向上抛。这个细节是事务逻辑是否正确的关键我自己写的时候差点搞反。第二种是生成器写法只用contextlib.contextmanager就能把普通函数变成上下文管理器代码量最少from contextlib import contextmanager contextmanager def change_dir(path): import os old_cwd os.getcwd() os.chdir(path) try: yield finally: os.chdir(old_cwd) with change_dir(/tmp): # 在这段代码里当前工作目录是 /tmp do_something()使用contextmanager时yield前面的代码相当于__enter__yield后面的代码相当于__exit__。如果yield后面想接异常处理逻辑需要把整个yield包在try/finally里——这个模式就是用生成器实现上下文管理器的标准模板。5.3 上下文管理器的高级玩法同时管理多个资源Python允许在一个with语句里同时进入多个上下文管理器比如同时打开两个文件做逐行对比with open(file_a.txt, encodingutf-8) as f_a, open(file_b.txt, encodingutf-8) as f_b: for x, y in zip(f_a, f_b): if x ! y: print(f差异行: {x.rstrip()} vs {y.rstrip()})这里两个文件都会自动关闭而且如果任何一个打开失败前面成功打开的资源也会自动关闭不会泄漏。这个特性在做数据对账脚本时非常实用我以前用普通写法时总担心某个分支忘记关文件用with之后就再没操心过。6. 参数魔法拆包与打包的底层逻辑6.1 *args和**kwargs一个星解序列两个星解字典函数参数里的*args与**kwargs本质上不是“关键字”本身特殊而是解包操作符。调用函数时*可以把一个可迭代对象拆分成位置参数**可以把一个字典拆分成关键字参数。def show(a, b, c): print(a, b, c) nums [1, 2, 3] show(*nums) # 等价于 show(1, 2, 3) info {a: 10, b: 20, c: 30} show(**info) # 等价于 show(a10, b20, c30)这个语法最核心的用途有这几个转发参数装饰器里的wrapper(*args, **kwargs)就是干这个的、合并配置、动态构建调用。一个函数可以接收别人传来的任意参数再原封不动传给另一个函数——模块解耦最常用的手段。6.2 解包不只是函数参数那里能用很多人不知道Python 3.5之后普通赋值语句和列表表达式也能用*解包了first, *rest, last [1, 2, 3, 4, 5] # first1, rest[2,3,4], last5 merged [*list_a, *list_b] # 合并列表等价于 list_a list_b combined {**dict_a, **dict_b} # 合并字典后面的键覆盖前面的解包语法在写配置合并、数据处理时能省出大量临时变量。但有一点我必须提醒解包太多会影响可读性。如果一段业务逻辑里连续出现三四处**、*那就是该抽函数了别把代码写成迷宫。6.3 关键字参数的地位比位置参数更稳进阶的道路上一个值得刻意养成的习惯是多参数接口尽量用关键字传参甚至是强制关键字参数。Python里在*后面的参数调用时只能用关键字传递def crew(name, *models, leadercaptain): ...更常用的是定义时直接加个独立*def upload_file(path, *, max_size_mb100, overwriteFalse): ...这个写法禁止了upload_file(a.txt, 200, True)这种全靠调用者记忆顺序的调用方式。参数一多位置参数的调用就是灾难调用方面对五个位置参数根本分不清第几个是端口、第几个是超时值。而强制关键字参数让每一个配置项自带名字代码自解释性显著提高。这个习惯在大项目协作里特别重要我review过的代码里凡是多个布尔参数叠加的位置传参几乎都会出现传错顺序的bug。7. 常见问题与排查经验7.1 生成器只能用一次iterator没有回头路很多人发现生成器“见鬼了”第一次for循环正常第二次for循环输出为空。这不叫bug而是迭代器的本性——它像一支只能往前走的箭耗尽后StopIteration已经抛出游标停在终点不会自动重置。我实际工作中最常踩的坑是写了一个get_rows()函数返回生成器调用方准备分两次统计第一次sum、第二次len结果第二次永远得到0。解决方案也简单要么每次调用都重新构建生成器要么把数据物化成列表。具体取舍的依据是数据量几十万条以下直接list数据大就loop两次但每次都重新读源。7.2 装饰器看不清函数名必须补functools.wraps试验上面不写functools.wraps的装饰器你会看到这样的诡异现象my_decorator def greet(): 打招呼 pass print(greet.__name__) # wrapper函数名变成wrapper之后影响是连锁的文档字符串丢了调试日志里全是wrapper某些依赖内省机制的框架行为还会变古怪比如pytest的fixture名匹配。所以我在自己的项目里定了条规矩任何装饰器内层都必须加functools.wraps(func)没有任何例外。7.3 闭包变量泄漏到循环之外除了上面提到的延迟绑定坑闭包还有一个隐蔽问题如果你在模块里直接写一个带循环的嵌套函数循环变量会泄漏到外层作用域。在旧版Python2.x时代这是经典作用域bugPython 3的列表推导式已经修掉了泄漏问题但普通for循环依然会泄漏变量。示例for j in range(5): pass print(j) # 输出4j泄漏了这个行为在代码量大的时候很难排查你可能会在几百行下面突然发现一个j但不知道它从哪来的。建议从一开始就养成习惯循环变量用完即弃别指望它“随循环结束而消失”也别在循环外面依赖它的值。7.4 上下文管理器里的异常被吞了写自定义__exit__时一个容易出问题的点是返回值。看这个伪代码def __exit__(self, exc_type, exc_val, exc_tb): clean_up() return True # 异常被吞__exit__返回True时解释器会认为“异常已经被处理”于是不再向上抛出。如果你只是想在退出时做清理而没打算吞掉业务异常那__exit__应该返回False或者不写return默认就是None相当于False。我处理事务时特别要求有异常主动rollback后继续返回True并且从上下文里抛出业务异常而普通资源清理场景千万别乱返回True。7.5 性能陷阱列表推导式虽好也要看场景列表推导式和生成器表达式看起来只是括号区别但这俩的内存差异极大。常见误用是对一个极大的序列做sum([x * 2 for x in huge])这会先生成一个完整的列表然后sum再遍历它等于同一份数据占了双倍内存。正确写法是sum(x * 2 for x in huge)直接用生成器惰性求和。同理判断是否有满足条件的元素用any(...)配合生成器表达式能在找到第一个匹配项时立刻短路不用遍历全量数据。这类写法优化在数据批量处理时效果非常明显。8. 进阶语法的联动几个综合应用8.1 装饰器闭包上下文管理器打造一个简易重试器学了这么多样语法它们不是孤岛。这里给一个综合案例一个带延迟退避的重试装饰器集成日志功能支持最大重试次数和退避因子。import functools import time import random def retry_with_backoff(max_attempts5, base_delay0.1, backoff_factor2): def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): current_delay base_delay for attempt in range(1, max_attempts 1): try: return func(*args, **kwargs) except (ConnectionError, TimeoutError) as e: if attempt max_attempts: raise sleep_time current_delay random.uniform(0, current_delay * 0.2) print(f[重试] {func.__name__} 第 {attempt} 次失败: {e}, {sleep_time:.2f}s 后重试) time.sleep(sleep_time) current_delay * backoff_factor return None return wrapper return decorator retry_with_backoff(max_attempts3, base_delay0.5) def fetch_user_from_api(user_id): # 模拟不稳定的网络请求 if random.random() 0.6: raise ConnectionError(网络暂不可用) return {id: user_id, name: Tom}这个例子融合了带参数的装饰器工厂模式、*args/**kwargs参数转发、闭包保存重试状态。你在实际的爬虫、RPC客户端、消息队列消费者里都能套用这个模板。我把它写进过好几个项目把耦合在业务代码里的重试逻辑全部抽成了装饰器业务函数清爽了很多。8.2 生成器字典解包处理配置文件的紧凑写法读取配置文件并合并默认配置是每个后端项目都会遇到的场景。用生成器表达式加字典解包代码可以写得非常紧凑且易读def load_config(path, default_configNone): defaults { host: 127.0.0.1, port: 8080, debug: False, } if default_config: defaults {**defaults, **default_config} with open(path, encodingutf-8) as f: items (line.strip().split(, 1) for line in f if in line and not line.strip().startswith(#)) parsed {k.strip(): v.strip() for k, v in items} return {**defaults, **parsed}这里用了几个关键点with管理文件句柄、生成器表达式按需清洗每一行、字典解包实现配置覆盖。这种组合写法的代码量比传统for循环加if判断少一半而且每行的意图都很明确。8.3 生成器处理大文件后接入上下文管理器一个地道的Python进阶开发者会自然而然地组合这些语法来解决问题。比如处理超大文件时要把“分块读取”的逻辑做成一个生成器函数再用上下文管理器保证打开和关闭的安全from contextlib import contextmanager contextmanager def big_file_reader(file_path, chunk_size1024 * 1024): try: f open(file_path, rb) def chunks(): while True: data f.read(chunk_size) if not data: break yield data yield chunks() finally: f.close() with big_file_reader(huge_data.bin) as chunks: for one_chunk in chunks: process(one_chunk)你会发现生成器嵌套在上下文管理器里上下文管理器负责生命周期生成器负责数据流分工非常清晰。这种配合是Python语言设计里非常优雅的一面——每个工具解决自己最擅长的问题。9. 写在笔记最后的一些个人体会这一篇笔记本想写得更短但写着写着还是塞了这么多内容。回头梳理整个Python所谓“进阶语法”的脉络我个人最大的体会是不要孤立地记语法点要把它们放进“协议”和“组合”的框架里理解。你只要想清楚for背后的迭代协议、背后的函数替换、with背后的资源协议那么无论遇到多花哨的写法都能拆解出来。所谓进阶其实是换了一种看代码的方式不再把Python当成一组“命令”而是理解成一组“对象之间的协作规则”。还有一个习惯我想特别分享读代码的效率远高于写代码的学习效率。找一份高质量的开源项目源码比如requests、Flask核心源码把里面出现的property、yield、contextmanager、*args圈出来试着用自己的话解释它为什么出现在这里、解决了什么问题。一遍不行就两遍比刷十遍语法教程都管用。另外这些语法特性在实际业务里使用时请遵循一条原则不加戏。能写简单for循环解决的就别为了炫技套三层生成器一个普通函数能搞定的不必非包个装饰器。语法进阶的意义是让你在真正需要的时候有工具可用而不是让你把代码写得让人看不懂。代码清晰永远是第一优先级一个人写得爽、三个人看得苦的“高级语法”说到底只是自嗨。最后再分享一个小技巧像itertools、functools、contextlib这三个标准库模块是Python高级语法的“武器库”。itertools.product做笛卡尔积、itertools.chain连接多个迭代器、functools.partial固化参数、functools.lru_cache做缓存这些函数式编程工具配合本篇讲的闭包和装饰器几乎能覆盖绝大多数需要“高级语法”解决的场景。我建议你把官方文档里这三个模块过一遍十分钟就能掌握十几个实用工具投入产出比远超想象。Python这条路没有所谓的“学完”语法进阶笔记二到此为止下一篇准备写写Python的类机制与元类、描述符协议那些才是真正触及Python灵魂的内容。先把今天这些消化透再说吧。
返回列表