为什么代码根据集合内的变量或生成器工作不同?
Why does code work different based on vars or generators inside a set?
目标是找出a**b的个数,其中2<=a,b<=100。任务很简单,我找到了答案,但我不明白为什么这很好用:
def count():
return len(set(a**b for a in range(2,101) for b in range(2,101)))
但这是错误的:
def count():
a = (i for i in range(2,101))
b = (i for i in range(2,101))
return len(set(i**j for i in a for j in b))
即使这样也能正常工作(基于第二个函数):return len(set(i**j for i in a for j in range(2, 101)))
但是把第一个var改成generator,对下一个做相反的操作,就出错了:
return len(set(i**j for i in range(2, 101) for j in a))
真的很想自己解决这个问题,但我就是不知道哪里出了问题
@ddejohn 给了你一个非常简短的答案,所以我会展开。
Python 的生成器的行为起初有点奇怪。这是一个简单的例子:
my_generator = (i for i in range(3)) # 0 to 2 included
print(next(my_generator)) # 0
print(next(my_generator)) # 1
print(next(my_generator)) # 2
print(next(my_generator)) # StopIteration raised
生成器将提供其值,然后引发 StopIteration
异常。
当生成器耗尽时对 next
的后续调用将继续引发 StopIteration
异常。
这意味着我们不能在生成器上循环两次,这是一个例子:
my_generator = (i for i in range(3)) # 0 to 2 included
for letter in ('a', 'b'):
print(letter)
for num in my_generator:
print(num)
print("end")
a
0
1
2
end
b
end
对于第一个字母,我们循环生成器值,直到它引发一个 StopIteration
,这表明 for
循环停止。对于第二个字母,生成器立即引发 StopIteration
因此循环根本没有进行迭代。
这是设计使然:生成器只能使用一次,它们会生成值直到耗尽。
回到您的代码,我将您的生成器 i**j for i in a for j in b
转换为两个循环以使其更易于打印:
def count():
a = (i for i in range(2,101))
b = (i for i in range(2,101))
for i in a:
print(f"outer loop {i=}")
for j in b:
print(f" inner loop {j=}")
count()
outer loop i=2
inner loop j=2
inner loop j=3
inner loop j=4
[...]
inner loop j=99
inner loop j=100
outer loop i=3
outer loop i=4
outer loop i=5
outer loop i=6
[...]
outer loop i=98
outer loop i=99
outer loop i=100
您可以看到 i
的第一次迭代是预期的,但其他的不是,因为 j
生成器已经耗尽。
这里有两种方法可以解决这个问题:
- 不使用生成器(因为这里你只有 ~100 个值,所以不需要生成器)
def count():
a = (i for i in range(2,101))
b = tuple(i for i in range(2,101))
# ^^^^^
for i in a:
print(f"outer loop {i=}")
for j in b:
print(f" inner loop {j=}")
count()
- 或者为每个 outer-iteration 构建一个新的生成器:
def count():
a = (i for i in range(2,101))
# not here
for i in a:
print(f"outer loop {i=}")
# but here :
b = (i for i in range(2,101))
for j in b:
print(f" inner loop {j=}")
count()
第二种解决方案是在您的代码版本中隐式完成的工作 (a**b for a in range(2,101) for b in range(2,101)
):在 a
的每次迭代中创建一个新的 range
对象。
我希望现在更清楚了。
吹毛求疵:(i for i in range(2,101))
几乎等同于简单的 range(2,101)
因为 range
对象已经是惰性对象,所以将它包装在显式生成器中不会增加任何内容。
目标是找出a**b的个数,其中2<=a,b<=100。任务很简单,我找到了答案,但我不明白为什么这很好用:
def count():
return len(set(a**b for a in range(2,101) for b in range(2,101)))
但这是错误的:
def count():
a = (i for i in range(2,101))
b = (i for i in range(2,101))
return len(set(i**j for i in a for j in b))
即使这样也能正常工作(基于第二个函数):return len(set(i**j for i in a for j in range(2, 101)))
但是把第一个var改成generator,对下一个做相反的操作,就出错了:
return len(set(i**j for i in range(2, 101) for j in a))
真的很想自己解决这个问题,但我就是不知道哪里出了问题
@ddejohn 给了你一个非常简短的答案,所以我会展开。
Python 的生成器的行为起初有点奇怪。这是一个简单的例子:
my_generator = (i for i in range(3)) # 0 to 2 included
print(next(my_generator)) # 0
print(next(my_generator)) # 1
print(next(my_generator)) # 2
print(next(my_generator)) # StopIteration raised
生成器将提供其值,然后引发 StopIteration
异常。
当生成器耗尽时对 next
的后续调用将继续引发 StopIteration
异常。
这意味着我们不能在生成器上循环两次,这是一个例子:
my_generator = (i for i in range(3)) # 0 to 2 included
for letter in ('a', 'b'):
print(letter)
for num in my_generator:
print(num)
print("end")
a
0
1
2
end
b
end
对于第一个字母,我们循环生成器值,直到它引发一个 StopIteration
,这表明 for
循环停止。对于第二个字母,生成器立即引发 StopIteration
因此循环根本没有进行迭代。
这是设计使然:生成器只能使用一次,它们会生成值直到耗尽。
回到您的代码,我将您的生成器 i**j for i in a for j in b
转换为两个循环以使其更易于打印:
def count():
a = (i for i in range(2,101))
b = (i for i in range(2,101))
for i in a:
print(f"outer loop {i=}")
for j in b:
print(f" inner loop {j=}")
count()
outer loop i=2
inner loop j=2
inner loop j=3
inner loop j=4
[...]
inner loop j=99
inner loop j=100
outer loop i=3
outer loop i=4
outer loop i=5
outer loop i=6
[...]
outer loop i=98
outer loop i=99
outer loop i=100
您可以看到 i
的第一次迭代是预期的,但其他的不是,因为 j
生成器已经耗尽。
这里有两种方法可以解决这个问题:
- 不使用生成器(因为这里你只有 ~100 个值,所以不需要生成器)
def count(): a = (i for i in range(2,101)) b = tuple(i for i in range(2,101)) # ^^^^^ for i in a: print(f"outer loop {i=}") for j in b: print(f" inner loop {j=}") count()
- 或者为每个 outer-iteration 构建一个新的生成器:
def count(): a = (i for i in range(2,101)) # not here for i in a: print(f"outer loop {i=}") # but here : b = (i for i in range(2,101)) for j in b: print(f" inner loop {j=}") count()
第二种解决方案是在您的代码版本中隐式完成的工作 (a**b for a in range(2,101) for b in range(2,101)
):在 a
的每次迭代中创建一个新的 range
对象。
我希望现在更清楚了。
吹毛求疵:(i for i in range(2,101))
几乎等同于简单的 range(2,101)
因为 range
对象已经是惰性对象,所以将它包装在显式生成器中不会增加任何内容。