
函数写多了早晚会撞上这几个概念。你把一个函数塞进另一个函数里Python 不会报错你写了个函数自己调自己跑着跑着就栈溢出了你看到别人代码里something挂在函数头上一脸懵你听说生成器省内存但不知道它和迭代器到底什么关系。这些不是花架子是 Python 从能写到写得好的分水岭。这篇内容就是把我自己踩过的坑、翻过的文档、写过的实际项目里关于函数进阶的东西系统梳理一遍涵盖嵌套函数、高阶函数、递归函数、命名空间、闭包、装饰器、生成器、迭代器这八个核心主题。不管你是刚学完函数基础想往上走一步还是写了半年 Python 但总觉得有些地方没吃透这篇都值得你花时间看完。每个概念我都会从为什么需要它讲起配上可直接运行的代码再补上实际开发中容易翻车的地方。1. 嵌套函数与作用域函数里面还能定义函数1.1 为什么要把函数写在函数里面刚学函数的时候大家都是在一个文件里平铺直叙地定义一堆函数互相调用。但写着写着你会发现有些函数天生就是为另一个函数服务的放到全局作用域里既污染命名空间又暴露了不该暴露的实现细节。这时候嵌套函数就派上用场了。嵌套函数说白了就是在def里面再写def。看个最简单的例子def outer(name): def inner(): print(f你好, {name}) inner() outer(张三) # 输出: 你好, 张三inner定义在outer内部外面访问不到inner它只服务于outer。这种封装带来的好处很直接减少全局命名空间的污染同时隐藏实现细节。你在写一个数据处理函数时中间可能有五六个辅助步骤每个步骤都定义成全局函数模块一导入满屏都是名字。用嵌套函数把它们收进去对外只暴露一个入口干净利落。另一个重要原因是闭包的需要。嵌套函数可以捕获外层函数的变量这是后面要讲的闭包的基础。没有嵌套函数就没有闭包。1.2 LEGB 规则Python 找变量的顺序理解嵌套函数绕不开 Python 的变量查找规则也就是常说的LEGB 原则L (Local)当前函数内部E (Enclosing)外层函数的作用域嵌套函数才有G (Global)模块级别B (Built-in)Python 内置命名空间Python 查找一个变量名时按 L → E → G → B 的顺序逐层找找到就停。看个例子x 全局 def outer(): x 外层 def inner(): x 内层 print(x) # 内层 inner() print(x) # 外层 outer() print(x) # 全局每一层都有自己的x互不干扰。但如果你在inner里想修改外层的x直接赋值是不行的Python 会认为你在inner里新建了一个局部变量。这时候需要nonlocal关键字def outer(): count 0 def inner(): nonlocal count count 1 return count return inner counter outer() print(counter()) # 1 print(counter()) # 2 print(counter()) # 3nonlocal明确告诉 Python我要改的是外层函数的变量不是新建局部变量。 这个关键字在实际开发中用得很多尤其是写计数器、状态管理器的时候。注意nonlocal只能用于嵌套函数场景不能跨模块使用。如果你在模块顶层用nonlocalPython 会直接报SyntaxError。1.3 嵌套函数的实际应用场景嵌套函数不是炫技它在实际项目中有明确的使用场景。我举几个自己常用的场景一数据校验的封装def create_validator(min_val, max_val): def validate(value): if not isinstance(value, (int, float)): raise TypeError(必须是数字) if not (min_val value max_val): raise ValueError(f值必须在 {min_val} 到 {max_val} 之间) return True return validate age_validator create_validator(0, 150) print(age_validator(25)) # True # age_validator(200) # ValueError场景二带前缀的日志函数def make_logger(prefix): def log(message): print(f[{prefix}] {message}) return log db_log make_logger(DB) api_log make_logger(API) db_log(连接成功) # [DB] 连接成功 api_log(请求超时) # [API] 请求超时这种模式的好处是你不需要每次都传前缀参数工厂函数帮你记住了配置。这就是闭包的雏形后面会详细展开。2. 高阶函数函数也是一等公民2.1 函数作为参数和返回值Python 里函数是一等公民first-class citizen意思是函数可以像普通变量一样被赋值、传参、返回。高阶函数的定义很简单接收函数作为参数或者返回一个函数的函数。先看函数作为参数def apply(func, value): return func(value) def square(x): return x ** 2 def cube(x): return x ** 3 print(apply(square, 5)) # 25 print(apply(cube, 5)) # 125apply就是一个高阶函数它不关心你传什么函数进来只管调用。这种设计让代码的复用性和灵活性大幅提升。再看函数作为返回值def power_factory(exp): def power(base): return base ** exp return power square power_factory(2) cube power_factory(3) print(square(4)) # 16 print(cube(4)) # 64power_factory返回了一个函数返回的函数记住了exp的值。这就是高阶函数和闭包的结合。2.2 map、filter、sorted 的实战用法Python 内置了几个经典的高阶函数日常开发中出场率极高。map对每个元素做统一变换nums [1, 2, 3, 4, 5] squared list(map(lambda x: x ** 2, nums)) print(squared) # [1, 4, 9, 16, 25]filter按条件筛选元素nums [1, 2, 3, 4, 5, 6, 7, 8] evens list(filter(lambda x: x % 2 0, nums)) print(evens) # [2, 4, 6, 8]sorted自定义排序规则students [ {name: 张三, score: 85}, {name: 李四, score: 92}, {name: 王五, score: 78}, ] by_score sorted(students, keylambda s: s[score], reverseTrue) for s in by_score: print(s[name], s[score]) # 李四 92 # 张三 85 # 王五 78sorted的key参数接收一个函数用来提取排序依据。这个设计比 Java 里实现Comparator接口简洁太多。2.3 自定义高阶函数的注意事项写自定义高阶函数时有几个坑我踩过第一参数校验不能省。你接收一个函数参数但调用者可能传进来一个非可调用对象。加个callable()检查def apply(func, value): if not callable(func): raise TypeError(f{func} 不是可调用对象) return func(value)第二注意函数的签名匹配。高阶函数内部调用传入的函数时参数个数和类型必须对得上。如果传入的函数签名不匹配运行时会报TypeError。在文档字符串里写清楚期望的函数签名能省很多沟通成本。第三性能考量。map和filter返回的是迭代器Python 3不是列表。如果你需要多次遍历结果记得先转成list否则第二次遍历就是空的。这个坑新手特别容易踩result map(lambda x: x * 2, [1, 2, 3]) print(list(result)) # [2, 4, 6] print(list(result)) # [] 空的迭代器已经耗尽了3. 递归函数自己调用自己的艺术3.1 递归的两个必要条件递归函数就是在函数体内调用自身。写递归必须满足两个条件缺一不可基准情况base case什么时候停止递归直接返回结果递归情况recursive case把问题缩小规模调用自身最经典的例子是阶乘def factorial(n): if n 1: # 基准情况 return 1 return n * factorial(n - 1) # 递归情况 print(factorial(5)) # 120执行过程是这样的factorial(5) 5 * factorial(4) 5 * 4 * factorial(3) 5 * 4 * 3 * factorial(2) 5 * 4 * 3 * 2 * factorial(1) 5 * 4 * 3 * 2 * 1 120每次调用都在等待下一层的结果直到基准情况返回然后逐层回溯计算。3.2 递归的调用栈与深度限制Python 对递归深度有默认限制通常是 1000 层。超过就报RecursionErrorimport sys print(sys.getrecursionlimit()) # 1000默认值 def infinite(n): return infinite(n 1) # infinite(0) # RecursionError: maximum recursion depth exceeded你可以用sys.setrecursionlimit()调大限制但我不建议这么做。原因很简单Python 不会对递归做尾调用优化。每次递归调用都会在内存中压入一个新的栈帧栈帧多了内存消耗大而且有崩溃风险。调大限制只是把崩溃推迟没有解决根本问题。实操心得如果一个问题用递归写出来深度可能超过几百层优先考虑用循环或迭代替代。递归适合解决天然分层的问题比如树遍历、分治算法而不是简单的线性累加。3.3 递归的经典应用斐波那契与目录遍历斐波那契数列带缓存的版本朴素递归算斐波那契效率极低因为大量重复计算# 低效版本n40 就要跑很久 def fib_naive(n): if n 1: return n return fib_naive(n - 1) fib_naive(n - 2)用functools.lru_cache加缓存性能提升是数量级的from functools import lru_cache lru_cache(maxsizeNone) def fib(n): if n 1: return n return fib(n - 1) fib(n - 2) print(fib(100)) # 354224848179261915075瞬间出结果lru_cache本质上是一个装饰器它把每次计算的结果缓存起来下次遇到相同参数直接返回。这是递归优化的利器。目录遍历递归遍历文件夹是实际工作中很常见的需求import os def list_files(path, indent0): for item in os.listdir(path): full_path os.path.join(path, item) print( * indent item) if os.path.isdir(full_path): list_files(full_path, indent 1) list_files(.)这种场景用递归就很自然因为目录结构本身就是递归定义的一个目录包含文件或子目录子目录又包含文件或子目录。3.4 递归转迭代的思路任何递归都可以转成迭代核心思路是用显式栈模拟函数调用栈。以阶乘为例def factorial_iter(n): result 1 stack list(range(1, n 1)) while stack: result * stack.pop() return result print(factorial_iter(5)) # 120对于树遍历这种复杂递归转迭代需要手动维护栈代码会复杂不少。我的建议是能用递归清晰表达的就用递归深度可能很大的才考虑转迭代。不要为了显得高级而强行把简单递归改成复杂的迭代。4. 命名空间与作用域变量到底住在哪4.1 命名空间的本质命名空间namespace说白了就是名字到对象的映射。Python 里一切皆对象变量名只是贴在对象上的标签。命名空间就是管理这些标签的字典。Python 有几种命名空间命名空间类型生命周期示例内置命名空间解释器启动到关闭print、len、int全局命名空间模块加载到程序结束模块顶层定义的变量局部命名空间函数调用到函数返回函数内部定义的变量闭包命名空间外层函数返回后仍存在嵌套函数捕获的变量你可以用globals()和locals()查看当前命名空间x 100 def demo(): y 200 print(局部:, locals()) print(全局变量 x:, globals()[x]) demo() # 局部: {y: 200} # 全局变量 x: 1004.2 global 与 nonlocal 的正确使用global声明变量来自全局命名空间nonlocal声明变量来自外层函数命名空间。两者的区别用一个例子说清楚count 0 def outer(): count 10 def inner(): nonlocal count # 指向 outer 的 count count 1 return count return inner f outer() print(f()) # 11 print(f()) # 12 print(count) # 0全局的 count 没变如果把nonlocal换成global改的就是全局的count了。注意滥用global是代码坏味道。它让函数的副作用变得隐蔽调试时很难追踪变量在哪里被改了。我的原则是能通过参数传递和返回值解决的绝不用 global。4.3 命名空间的实际影响变量遮蔽问题命名空间最容易引发的问题是变量遮蔽shadowing。看这个例子list [1, 2, 3] # 把内置的 list 类型给覆盖了 # 后面想用 list() 创建列表就报错了 # new_list list((4, 5, 6)) # TypeError: list object is not callable用内置函数名当变量名会把内置命名空间里的名字遮蔽掉。类似的还有sum、max、min、id、type这些。这类 bug 特别隐蔽因为定义的时候不报错用的时候才炸。避免方法很简单变量命名避开内置函数名。如果你不确定某个名字是不是内置的可以这样检查import builtins print(hasattr(builtins, list)) # True说明 list 是内置名5. 闭包函数记住了它的出生环境5.1 闭包的定义与三要素闭包closure是嵌套函数的一个特殊形态。构成闭包需要三个条件存在嵌套函数内层函数引用了外层函数的变量外层函数返回内层函数def counter(): count 0 def increment(): nonlocal count count 1 return count return increment c counter() print(c()) # 1 print(c()) # 2 print(c()) # 3counter()执行完后按理说count应该被销毁了。但因为increment引用了它Python 把count保留在了闭包环境里。你可以用__closure__查看print(c.__closure__) # (cell at 0x...: int object at 0x...,) print(c.__closure__[0].cell_contents) # 35.2 闭包 vs 普通函数本质区别普通函数每次调用都是从零开始闭包则携带了状态。这个区别在需要记住某些信息的场景下非常有用。举个实际例子写一个带计数的 API 调用器def api_caller(base_url): call_count 0 def call(endpoint): nonlocal call_count call_count 1 print(f第 {call_count} 次调用: {base_url}/{endpoint}) # 实际请求逻辑... return call user_api api_caller(https://api.example.com/users) user_api(list) # 第 1 次调用: https://api.example.com/users/list user_api(detail) # 第 2 次调用: https://api.example.com/users/detailbase_url和call_count都被闭包记住了不需要每次调用都传。5.3 闭包的常见陷阱循环变量捕获这是闭包最经典的坑几乎每个人都踩过funcs [] for i in range(3): funcs.append(lambda: i) for f in funcs: print(f()) # 输出: 2 2 2不是 0 1 2原因是 lambda 里的i是引用不是值。循环结束后i的值是 2所有 lambda 看到的都是同一个i。修复方法是用默认参数把当前值固定下来funcs [] for i in range(3): funcs.append(lambda ii: i) # 默认参数在定义时求值 for f in funcs: print(f()) # 输出: 0 1 2或者用工厂函数def make_func(i): return lambda: i funcs [make_func(i) for i in range(3)]实操心得只要你在循环里创建闭包或 lambda就要警惕变量捕获问题。默认参数绑定是最简洁的修复方式但要注意默认参数在函数定义时就求值这个特性。6. 装饰器不修改原函数就能增强功能6.1 装饰器的本质语法糖而已装饰器的写法是decorator放在函数定义上面。很多人觉得它神秘其实它就是个语法糖my_decorator def my_func(): pass完全等价于def my_func(): pass my_func my_decorator(my_func)就这么简单。装饰器接收一个函数返回一个新函数通常是包装后的版本。6.2 手写一个完整的装饰器从零写一个计时装饰器把每个细节讲清楚import time from functools import wraps def timer(func): wraps(func) # 保留原函数的元信息 def wrapper(*args, **kwargs): start time.time() result func(*args, **kwargs) elapsed time.time() - start print(f{func.__name__} 耗时 {elapsed:.4f} 秒) return result return wrapper timer def slow_task(n): time.sleep(n) return 完成 print(slow_task(1)) # slow_task 耗时 1.0003 秒 # 完成几个关键点*args, **kwargs让 wrapper 能接收任意参数适配被装饰函数的各种签名wraps(func)保留原函数的__name__、__doc__等属性不加的话slow_task.__name__会变成wrapperwrapper 内部先做增强逻辑再调用原函数最后返回结果6.3 带参数的装饰器装饰器本身也可以接收参数需要多包一层def repeat(times): def decorator(func): wraps(func) def wrapper(*args, **kwargs): for _ in range(times): result func(*args, **kwargs) return result return wrapper return decorator repeat(3) def say_hello(name): print(f你好, {name}) say_hello(张三) # 你好, 张三 # 你好, 张三 # 你好, 张三结构是三层最外层接收装饰器参数中间层接收函数最内层是实际的包装逻辑。理解这个三层结构带参数的装饰器就不难了。6.4 装饰器的实际应用日志、缓存、权限校验装饰器在实际项目中的出场率极高我列几个常用场景日志记录def log_calls(func): wraps(func) def wrapper(*args, **kwargs): print(f调用 {func.__name__}, 参数: {args}, {kwargs}) return func(*args, **kwargs) return wrapper缓存自己实现简化版def memoize(func): cache {} 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 1: return n return fib(n - 1) fib(n - 2) print(fib(50)) # 12586269025很快权限校验def require_admin(func): wraps(func) def wrapper(user, *args, **kwargs): if user.get(role) ! admin: raise PermissionError(需要管理员权限) return func(user, *args, **kwargs) return wrapper require_admin def delete_user(user, user_id): print(f删除用户 {user_id}) admin {name: 管理员, role: admin} delete_user(admin, 100) # 删除用户 100注意多个装饰器叠加时执行顺序是从下往上装饰从上往下执行。这个顺序搞反了会导致逻辑错误写的时候一定要想清楚。7. 生成器按需生产的省内存利器7.1 生成器函数与 yield生成器函数就是用yield代替return的函数。调用生成器函数不会立即执行函数体而是返回一个生成器对象每次next()才执行到下一个yielddef count_up(n): i 0 while i n: yield i i 1 gen count_up(5) print(next(gen)) # 0 print(next(gen)) # 1 for num in gen: print(num) # 2 3 4关键区别return一次性返回所有结果yield每次产出一个值后暂停保留当前状态下次从暂停处继续。7.2 生成器的内存优势用数据说话假设你要处理一个 1000 万行的日志文件找出所有包含 ERROR 的行。用列表的方式def read_errors_list(path): result [] with open(path) as f: for line in f: if ERROR in line: result.append(line) return result # 1000 万行如果都匹配内存直接爆掉用生成器def read_errors_gen(path): with open(path) as f: for line in f: if ERROR in line: yield line for error in read_errors_gen(app.log): process(error) # 一次只处理一行内存占用恒定生成器版本无论文件多大内存占用都是常数级别。这就是惰性求值的威力。7.3 生成器表达式与 yield from生成器表达式长得像列表推导式但用圆括号# 列表推导式立即生成所有元素 squares_list [x ** 2 for x in range(1000000)] # 占大量内存 # 生成器表达式按需生成 squares_gen (x ** 2 for x in range(1000000)) # 几乎不占内存yield from用来委托给另一个可迭代对象简化嵌套生成器def chain_generators(*iterables): for it in iterables: yield from it result list(chain_generators([1, 2], [3, 4], [5, 6])) print(result) # [1, 2, 3, 4, 5, 6]7.4 生成器的 send 与 close生成器不只是产出值还能接收值通过send()方法def accumulator(): total 0 while True: value yield total if value is None: break total value acc accumulator() next(acc) # 启动生成器返回 0 print(acc.send(10)) # 10 print(acc.send(20)) # 30 print(acc.send(5)) # 35 acc.close() # 关闭生成器send()把值传给yield表达式生成器继续执行。这个特性让生成器可以当协程用是异步编程的基础之一。实操心得生成器一旦耗尽就不能重用。如果你需要多次遍历要么重新创建生成器要么转成列表。这个坑我在处理数据管道时踩过调试了半天才发现是生成器被耗尽了。8. 迭代器for 循环背后的机制8.1 可迭代对象与迭代器的区别这两个概念经常被混淆但区别很明确可迭代对象Iterable实现了__iter__()方法能被for遍历迭代器Iterator同时实现了__iter__()和__next__()方法列表、元组、字符串、字典都是可迭代对象但不是迭代器。iter()函数把可迭代对象转成迭代器nums [1, 2, 3] print(hasattr(nums, __iter__)) # True print(hasattr(nums, __next__)) # False列表不是迭代器 it iter(nums) print(hasattr(it, __next__)) # True迭代器 print(next(it)) # 1 print(next(it)) # 2for循环的本质就是先调iter()拿到迭代器然后不断调next()直到抛StopIteration异常。8.2 自定义迭代器实现一个迭代器需要写__iter__和__next__两个方法class CountDown: def __init__(self, start): self.current start def __iter__(self): return self def __next__(self): if self.current 0: raise StopIteration value self.current self.current - 1 return value for num in CountDown(5): print(num) # 5 4 3 2 1__iter__返回迭代器自身__next__返回下一个值没有值了就抛StopIteration。8.3 迭代器与生成器的关系生成器是迭代器的一种便捷实现。你写一个生成器函数Python 自动帮你实现了__iter__和__next__def my_gen(): yield 1 yield 2 g my_gen() print(hasattr(g, __iter__)) # True print(hasattr(g, __next__)) # True所以生成器天然就是迭代器可以直接用for遍历也可以用next()取值。区别在于生成器是写起来更简单的迭代器你不需要手动维护状态和抛异常yield帮你搞定一切。8.4 itertools迭代器的瑞士军刀Python 标准库的itertools模块提供了大量迭代器工具用好了能省很多代码import itertools # 无限计数器 counter itertools.count(start1, step2) print([next(counter) for _ in range(5)]) # [1, 3, 5, 7, 9] # 循环重复 cycler itertools.cycle([A, B, C]) print([next(cycler) for _ in range(7)]) # [A, B, C, A, B, C, A] # 组合 combos list(itertools.combinations([1, 2, 3, 4], 2)) print(combos) # [(1,2), (1,3), (1,4), (2,3), (2,4), (3,4)] # 分组按 key 分组需先排序 data [(A, 1), (A, 2), (B, 3), (B, 4)] for key, group in itertools.groupby(data, keylambda x: x[0]): print(key, list(group)) # A [(A, 1), (A, 2)] # B [(B, 3), (B, 4)]itertools里的函数返回的都是迭代器内存友好适合处理大数据流。9. 常见问题与排查技巧实录9.1 函数进阶常见报错速查表报错信息原因解决方法UnboundLocalError函数内赋值导致变量被当作局部变量用global或nonlocal声明RecursionError递归深度超过限制改用迭代或加缓存减少递归层数TypeError: NoneType object is not callable装饰器忘了return wrapper检查装饰器是否返回了函数StopIteration迭代器耗尽后继续next()用for循环或捕获异常TypeError: list object is not callable变量名遮蔽了内置函数重命名变量闭包中变量值不对循环变量捕获问题用默认参数绑定当前值9.2 装饰器调试技巧装饰器出问题时最难的是定位。因为报错栈里显示的是wrapper不是原函数。几个实用技巧第一务必加wraps。不加的话func.__name__变成wrapper日志和调试信息全乱了。第二用functools.wraps后还能拿到原函数。通过func.__wrapped__可以访问被装饰的原函数timer def my_func(): pass print(my_func.__wrapped__) # 原始函数对象第三装饰器里加异常捕获打印更多上下文def debug_decorator(func): wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: print(f函数 {func.__name__} 出错: {e}) print(f参数: args{args}, kwargs{kwargs}) raise return wrapper9.3 生成器与迭代器的性能对比实测我做过一个简单的性能测试处理 100 万个整数求和import time import sys # 列表方式 start time.time() total sum([x for x in range(1000000)]) print(f列表: {time.time() - start:.4f}秒, 内存: {sys.getsizeof([x for x in range(1000000)])} 字节) # 生成器方式 start time.time() total sum((x for x in range(1000000))) print(f生成器: {time.time() - start:.4f}秒)实测下来生成器版本在内存上优势巨大列表版本占用约 8MB生成器版本几乎为 0速度上差距不大。所以处理大数据时优先用生成器这是没有争议的。9.4 闭包内存泄漏的排查闭包会持有外层变量的引用如果外层变量是个大对象就会导致内存无法释放。看这个例子def create_handler(): big_data [i for i in range(1000000)] # 大列表 def handler(): return len(big_data) # 闭包持有了 big_data return handler h create_handler() # big_data 无法被回收因为 h 的闭包引用了它如果handler其实不需要big_data就会造成浪费。排查方法是用gc模块查看引用import gc gc.collect() for obj in gc.get_objects(): if isinstance(obj, list) and len(obj) 100000: print(f大列表: {len(obj)} 个元素)实操心得写闭包时只捕获真正需要的变量。如果外层有大对象考虑把它作为参数传入而不是直接捕获。10. 把这些概念串起来一个综合实战10.1 需求实现一个带缓存和重试的数据获取器把前面学的概念综合起来写一个实际可用的工具从某个数据源获取数据带缓存避免重复请求、带重试网络不稳定时自动重试、带日志记录每次调用。import time from functools import wraps def retry(max_attempts3, delay1): 带参数的重试装饰器 def decorator(func): wraps(func) def wrapper(*args, **kwargs): for attempt in range(1, max_attempts 1): try: return func(*args, **kwargs) except Exception as e: print(f第 {attempt} 次尝试失败: {e}) if attempt max_attempts: raise time.sleep(delay) return wrapper return decorator def memoize(func): 缓存装饰器用闭包保存缓存字典 cache {} wraps(func) def wrapper(*args): if args not in cache: cache[args] func(*args) return cache[args] return wrapper def log_calls(func): 日志装饰器 wraps(func) def wrapper(*args, **kwargs): print(f调用 {func.__name__}({args})) result func(*args, **kwargs) print(f{func.__name__} 返回: {result}) return result return wrapper log_calls memoize retry(max_attempts3, delay0.5) def fetch_data(key): 模拟数据获取key 为 error 时会失败 if key error: raise ConnectionError(模拟网络错误) return f数据-{key} # 测试 print(fetch_data(user_1)) print(fetch_data(user_1)) # 第二次走缓存不会重复调用这个例子用到了装饰器三层叠加、闭包memoize 的 cache、高阶函数装饰器本身就是、异常处理。装饰器的执行顺序是log_calls最外层retry最内层所以日志会记录重试后的最终结果。10.2 用生成器实现一个数据管道再写一个生成器版本的数据处理管道模拟从读取到过滤到转换的流程def read_lines(text): 逐行读取 for line in text.split(\n): yield line.strip() def filter_empty(lines): 过滤空行 for line in lines: if line: yield line def parse_kv(lines): 解析 keyvalue 格式 for line in lines: if in line: key, value line.split(, 1) yield key.strip(), value.strip() raw name张三 age25 city北京 score92 pipeline parse_kv(filter_empty(read_lines(raw))) for key, value in pipeline: print(f{key}: {value}) # name: 张三 # age: 25 # city: 北京 # score: 92每个生成器只负责一个步骤通过yield串联起来。数据像流水一样经过每个环节内存占用极低。这种管道模式在处理日志、CSV、大数据流时非常实用。10.3 递归实现一个树形结构遍历最后用递归处理一个树形结构比如评论回复comments { id: 1, text: 主评论, replies: [ { id: 2, text: 回复1, replies: [ {id: 4, text: 回复1的子回复, replies: []} ] }, { id: 3, text: 回复2, replies: [] } ] } def print_comments(node, level0): print( * level f- {node[text]}) for reply in node[replies]: print_comments(reply, level 1) print_comments(comments) # - 主评论 # - 回复1 # - 回复1的子回复 # - 回复2树形结构天然适合递归因为每个节点的子节点又是一棵树。这种场景用迭代写反而复杂。11. 学习路径与进阶建议11.1 这八个概念的掌握顺序如果你是按顺序学的建议这个顺序嵌套函数 → 命名空间 → 闭包 → 高阶函数 → 装饰器 → 递归 → 迭代器 → 生成器。理由是这样嵌套函数和命名空间是基础闭包建立在嵌套函数之上装饰器建立在闭包和高阶函数之上迭代器和生成器是另一条线但和函数式编程相关。递归相对独立可以穿插着学。11.2 哪些概念在实际工作中用得最多按我的经验出场率从高到低装饰器几乎每个项目都有日志、缓存、权限、重试生成器处理大数据、流式处理必备高阶函数map、filter、sorted天天用闭包写工具函数、状态管理时用迭代器理解for机制自定义数据结构时用递归树形结构、分治算法时用嵌套函数封装辅助逻辑时用命名空间理解作用域排查 bug 时用11.3 容易混淆的概念对比最后整理几个容易搞混的点概念 A概念 B区别可迭代对象迭代器前者有__iter__后者还有__next__生成器迭代器生成器是迭代器的便捷实现用yield写闭包普通嵌套函数闭包的外层函数返回了内层函数且内层引用了外层变量globalnonlocal前者指向模块级后者指向外层函数级returnyield前者结束函数后者暂停并保留状态我在实际带新人的过程中发现这几个概念只要动手写一遍再对照着调试一下基本就通了。光看不动手看十遍还是懵。建议你把文中的代码都敲一遍改改参数看看报错感受会完全不一样。尤其是装饰器和生成器不亲手写几个很难真正理解它们的执行流程。