我正在摆弄Python的生成器和可迭代类,只是为了好玩。基本上我想测试一些我从未太确定过的东西:Pythons中的类有一些显着的开销,最好依赖实现yield
的方法而不是实现迭代器协议的类,如果可以的话
我无法在Google中找到关于此主题的令人满意的解释,所以我决定使用这两个简单的脚本自行测试它们:func_iter.py
和class_iter.py
这是func_iter.py
:
#!/usr/bin/env python
import time
x = 0
def create_generator(num):
mylist = range(num)
for i in mylist:
yield i
t = time.time()
gen = create_generator(100000)
for i in gen:
x = x + i
print "%.3f" % (time.time() - t)
这里是class_iter.py
:
#!/usr/bin/env python
import time
x = 0
class Generator(object):
def __init__(self, num):
self.start = 0
self.end = num
def __iter__(self):
return self
def next(self):
if self.start == self.end:
raise StopIteration
else:
self.start = self.start + 1
return self.start
t = time.time()
gen = Generator(100000)
for i in gen:
x = x + i
print "%.3f" % (time.time() - t)
然后我在bash中使用它们运行了10次(例如class_iter.py
):
for i in {1..10}; do ./class_iter.py; done
以下是每个人的平均运行时间:
class_iter.py: 0.0864
func_iter.py: 0.0307
现在,我的问题是:
class_iter.py
花费的时间几乎是func_iter.py
的三倍?编辑:正如Dacav建议的那样,我也尝试使用func_iter.py
而不是xrange
来运行range
。这将其平均运行时间减少到0.0263秒。
答案 0 :(得分:5)
类版本花费大量时间访问自己的变量。每个self.whatever
成本周期。如果将__iter__
定义为生成器并最小化实例变量的使用,则类和函数版本之间的差异可以忽略不计:
setup = """
def create_generator(num):
mylist = range(num)
for i in mylist:
yield i
class Generator(object):
def __init__(self, num):
self.start = 0
self.end = num
def __iter__(self):
return self
def next(self):
if self.start == self.end:
raise StopIteration
else:
self.start = self.start + 1
return self.start
class Generator2(object):
def __init__(self, num):
self.mylist = range(num)
def __iter__(self):
for i in self.mylist:
yield i
"""
import timeit
print timeit.timeit('for p in create_generator(1000):p', setup, number=1000)
print timeit.timeit('for p in Generator(1000):p', setup, number=1000)
print timeit.timeit('for p in Generator2(1000):p', setup, number=1000)
结果:
0.158941984177
0.696810007095
0.160784959793
所以第二个生成器类几乎与函数版本一样快。
请注意示例中的Generator
和Generator2
并不完全等效,有些情况下您不能简单地用生成器替换“普通”迭代器(例如编组)。
答案 1 :(得分:1)
如果您正在使用python,那么很有可能您不是针对软件性能,而是您更关心在开发中快速灵活。
说,我认为只要您的代码足够智能以避免对一个解决方案的偏见,比较方法就相当公平。
例如,基于yield
的版本的可能改进可能是删除range
功能广告而是使用xrange
功能。差异(在python 2.x中)是range
构建一个值列表(因此它必须在内存中为它分配空间),而xrange
构建一个可在给定值范围内的可迭代对象。
答案 2 :(得分:1)
你似乎完全正确,你的比较是公平的。当您仅比较开销时,支持迭代器协议的类将比生成器函数慢。
然而,在现实世界中,如果代码足够复杂以证明一个类,算法的运行时间将使开销相形见绌,因此它将与运行时完全无关。你的计划。
你在这里担心微观优化。你不应该。专注于编写好的,可读的代码,并使用正确的算法来完成工作。在类版本中花在属性查找和方法调用上的时间不会成为你的瓶颈。