为什么 list(generator) 与 for i in generator 不同?
Why is list(generator) differ from for i in generator?
为什么生成器 func2(func1(init())) 的行为因我的打印方式而异?
def func1(x):
z = ["a", "b"]
for i in x:
for z1 in z:
i["f1"] = z1
yield i
def func2(x):
z = ["c", "d"]
for i in x:
for z1 in z:
i["f2"] = z1
yield i
def init():
for i in range(1, 3):
yield {"f0": i}
def main():
print(list(func2(func1(init()))))
print("\n")
for i in func2(func1(init())):
print(i)
if __name__ == '__main__':
main()
第一次打印 returns:
[{'f0': 1, 'f1': 'b', 'f2': 'd'}, {'f0': 1, 'f1': 'b', 'f2': 'd'}, {'f0': 1, 'f1': 'b', 'f2': 'd'}, {'f0': 1, 'f1': 'b', 'f2': 'd'}, {'f0': 2, 'f1': 'b', 'f2': 'd'}, {'f0': 2, 'f1': 'b', 'f2': 'd'}, {'f0': 2, 'f1': 'b', 'f2': 'd'}, {'f0': 2, 'f1': 'b', 'f2': 'd'}]
第二次打印 returns:
{'f0': 1, 'f1': 'a', 'f2': 'c'}
{'f0': 1, 'f1': 'a', 'f2': 'd'}
{'f0': 1, 'f1': 'b', 'f2': 'c'}
{'f0': 1, 'f1': 'b', 'f2': 'd'}
{'f0': 2, 'f1': 'a', 'f2': 'c'}
{'f0': 2, 'f1': 'a', 'f2': 'd'}
{'f0': 2, 'f1': 'b', 'f2': 'c'}
{'f0': 2, 'f1': 'b', 'f2': 'd'}
我还提到,如果我将“yield i”更改为“yield i.copy()”,打印结果将匹配。
有人可以解释差异吗?
您正在循环中重复使用字典 i
。内部循环 for z1 in z:
指向同一个字典 i
,每个 yield 都会发生变化,yield 返回相同的引用字典。
print(i)
在 for 循环中打印它们,然后再完成所有 属性 突变。
但是,list(*)
在生成器函数中的所有 i["key"]=
突变副作用完成后输出。
如果你使用 .append
而不是 print(i)
你会得到相同的输出,尽管 i.copy()
输出可能是你想要的。
这是无副作用和不变性通常是推荐模式的原因之一的示例。
def func1(x):
z = ["a", "b"]
for i in x:
# reusing the same dictionary 'i' for multiple z1 values
for z1 in z:
i["f1"] = z1 # mutates i["f1"]
yield i # yields the same referenced dictionary for all z1 in z
def func2(x):
z = ["c", "d"]
for i in x:
for z1 in z:
i["f2"] = z1
yield i #.copy() to fix
def init():
for i in range(1, 3):
yield {"f0": i}
def main():
x = list(func2(func1(init())))
print(x)
x[0]['f0']='mutated'
print(x) # notice 'mutated' affects multiple elements coinciding with an "i in x"
print("\n")
x = []
for i in func2(func1(init())):
print(i)
x.append(i)
x[0]['f0']='mutated'
print(x) # notice that mutated appears in multiple elements (half of them in this case)
if __name__ == '__main__':
main()
注:我在func1上加了注释,因为思路是一样的。但是示例代码中的问题正在影响 func2,因为它主要影响提供对 list() 函数的字典引用的外循环。理想情况下,您应该在两个函数上使用 i.copy() (或者如果您需要大量优化,则使用某种惰性复制评估)以防止意外行为,例如,如果您使用 func1(func2(init()))
.
注意:我主要假设您提供的第二个示例是您期望的行为(通常是期望的结果)。想要相同的引用字典和上面的代码(通常在某种对象初始化避免优化的上下文中)是有效的用例,尽管它使代码更难推理。通常能够进行对象引用比较是更有用的优化,并且不必跟踪函数中的副作用使代码更容易推理。当涉及生成器产生副作用时,代码可读性尤其成问题。
为什么生成器 func2(func1(init())) 的行为因我的打印方式而异?
def func1(x):
z = ["a", "b"]
for i in x:
for z1 in z:
i["f1"] = z1
yield i
def func2(x):
z = ["c", "d"]
for i in x:
for z1 in z:
i["f2"] = z1
yield i
def init():
for i in range(1, 3):
yield {"f0": i}
def main():
print(list(func2(func1(init()))))
print("\n")
for i in func2(func1(init())):
print(i)
if __name__ == '__main__':
main()
第一次打印 returns:
[{'f0': 1, 'f1': 'b', 'f2': 'd'}, {'f0': 1, 'f1': 'b', 'f2': 'd'}, {'f0': 1, 'f1': 'b', 'f2': 'd'}, {'f0': 1, 'f1': 'b', 'f2': 'd'}, {'f0': 2, 'f1': 'b', 'f2': 'd'}, {'f0': 2, 'f1': 'b', 'f2': 'd'}, {'f0': 2, 'f1': 'b', 'f2': 'd'}, {'f0': 2, 'f1': 'b', 'f2': 'd'}]
第二次打印 returns:
{'f0': 1, 'f1': 'a', 'f2': 'c'}
{'f0': 1, 'f1': 'a', 'f2': 'd'}
{'f0': 1, 'f1': 'b', 'f2': 'c'}
{'f0': 1, 'f1': 'b', 'f2': 'd'}
{'f0': 2, 'f1': 'a', 'f2': 'c'}
{'f0': 2, 'f1': 'a', 'f2': 'd'}
{'f0': 2, 'f1': 'b', 'f2': 'c'}
{'f0': 2, 'f1': 'b', 'f2': 'd'}
我还提到,如果我将“yield i”更改为“yield i.copy()”,打印结果将匹配。 有人可以解释差异吗?
您正在循环中重复使用字典 i
。内部循环 for z1 in z:
指向同一个字典 i
,每个 yield 都会发生变化,yield 返回相同的引用字典。
print(i)
在 for 循环中打印它们,然后再完成所有 属性 突变。
但是,list(*)
在生成器函数中的所有 i["key"]=
突变副作用完成后输出。
如果你使用 .append
而不是 print(i)
你会得到相同的输出,尽管 i.copy()
输出可能是你想要的。
这是无副作用和不变性通常是推荐模式的原因之一的示例。
def func1(x):
z = ["a", "b"]
for i in x:
# reusing the same dictionary 'i' for multiple z1 values
for z1 in z:
i["f1"] = z1 # mutates i["f1"]
yield i # yields the same referenced dictionary for all z1 in z
def func2(x):
z = ["c", "d"]
for i in x:
for z1 in z:
i["f2"] = z1
yield i #.copy() to fix
def init():
for i in range(1, 3):
yield {"f0": i}
def main():
x = list(func2(func1(init())))
print(x)
x[0]['f0']='mutated'
print(x) # notice 'mutated' affects multiple elements coinciding with an "i in x"
print("\n")
x = []
for i in func2(func1(init())):
print(i)
x.append(i)
x[0]['f0']='mutated'
print(x) # notice that mutated appears in multiple elements (half of them in this case)
if __name__ == '__main__':
main()
注:我在func1上加了注释,因为思路是一样的。但是示例代码中的问题正在影响 func2,因为它主要影响提供对 list() 函数的字典引用的外循环。理想情况下,您应该在两个函数上使用 i.copy() (或者如果您需要大量优化,则使用某种惰性复制评估)以防止意外行为,例如,如果您使用 func1(func2(init()))
.
注意:我主要假设您提供的第二个示例是您期望的行为(通常是期望的结果)。想要相同的引用字典和上面的代码(通常在某种对象初始化避免优化的上下文中)是有效的用例,尽管它使代码更难推理。通常能够进行对象引用比较是更有用的优化,并且不必跟踪函数中的副作用使代码更容易推理。当涉及生成器产生副作用时,代码可读性尤其成问题。