• 隐藏侧边栏
  • 展开分类目录
  • 关注微信公众号
  • 我的GitHub
  • QQ:1753970025
Chen Jiehua

Python生成器的一个坑 

用Python写迭代器(Iterator)的时候,可能会有人向你推荐生成器(Generator),并列举一堆生成器的好处。不过,今天要来分享一个生成器的坑……

假如我们要来写一个归一化函数,简单看个例子:

def normalize(numbers):
	total = sum(numbers)
	result = []
	for v in numbers:
		p = 100.0 * v / total
		result.append(p)
	return result


def main():
	data = [12, 20, 35, 9]
	print normalize(data)

正常情况下输出结果非常符合预期:

[15.789473684210526, 26.31578947368421, 46.05263157894737, 11.842105263157896]

但……

如果 numbers 传入了一个generator,那结果会变成什么样子呢?我们来试一下:

def gen_data(data):
	for v in data:
		yield v
		
def main():
	data = [12, 20, 35, 9]
	gen = gen_data(data)
	print normalize(gen)

猜猜结果输出什么:

[]

没错,是空列表!怎么回事呢?是for循环里的生成器坏了,试试直接输出:

def main():
	data = [12, 20, 35, 9]
	gen = gen_data(data)
	for v in gen:
		print v

输出结果:

12
20
35
9

没问题呀!

那……

排除生成器本身的问题,那肯定是normalize函数错了。断点看一下:

def normalize(numbers):
	total = sum(numbers)                  -------> 这里没错,total=76
	result = []
	for v in numbers:                     -------> for循环竟然直接跳过了
		p = 100.0 * v / total
		result.append(p)
	return result

仔细想想,生成器的原理是什么?调用next() 获取下一个元素的值,直到返回 StopIteration。

这样子就说得通了,sum() 把生成器遍历了一遍,等到for循环的时候,已经没有内容需要遍历了。

def main():
	data = [12, 20, 35, 9]
	gen = gen_data(data)
	t1 = sum(gen)
	t2 = sum(gen)
	print t1, t2

结果输出:

76 0

改……

这里提供一个简单的解决方法:

class GenData(object):
	def __init__(self, data):
		self.data = data
		
	def __iter__(self):
		for v in self.data:
			yield v
			

至于为啥,留给你思考一下。

码字很辛苦,转载请注明来自ChenJiehua《Python生成器的一个坑》

评论