Python yield生成器内list.copy()为何无法隔离原列表修改?
问题核心原因
两种写法结果不一致的本质是 lst.copy()的执行时机完全不同,和浅拷贝、深拷贝的特性没有关系。
带yield的生成器函数执行逻辑
使用yield定义的函数是生成器函数,调用这类函数时不会立刻执行函数内部的任何代码,只会返回一个处于暂停状态的生成器对象,暂停点在函数的第一行代码之前。
只有当你第一次迭代这个生成器对象时,函数体才会从头开始逐行执行。
对应第一版代码,实际执行顺序是:
- 初始化原列表
lst = [Name("Tom"), Name("Tommy")] - 调用
generator(lst)拿到生成器对象gen,此时函数内的lst.copy()完全没有执行 - 执行
lst[0] = Name("Andrea"),替换原列表的第一个元素 - 进入
for name in gen循环,触发生成器执行:此时才运行lst.copy()创建列表副本,副本复制的是已经被修改过的原列表,第一个元素已经是Name("Andrea"),最终输出自然是Andrea和Tommy。
返回生成器表达式版本的执行逻辑
第二版写法里的generator是普通函数,不是生成器函数——因为函数内部没有yield关键字,只是直接返回了一个生成器表达式对象。
调用普通函数时,函数内部的代码会立刻同步执行:
- 初始化原列表
lst = [Name("Tom"), Name("Tommy")] - 调用
generator(lst),函数当场执行lst.copy(),此时原列表还没被修改,创建的副本里存的是最初两个Name对象的引用,生成器表达式绑定的是这个提前创建好的副本 - 执行
lst[0] = Name("Andrea")修改原列表,这个操作不会影响已经创建好的列表副本 - 迭代生成器时,遍历的是提前创建的未被修改的副本,自然输出Tom和Tommy。
补充验证:在第一版yield写法中,拿到生成器后先调用一次
next(gen),触发函数内的copy逻辑执行完,再去修改原列表,后续的生成器输出就不会被原列表的修改影响了。
内容的提问来源于stack exchange,提问作者ddejohn
相关产品推荐
相关产品推荐

