问题描述
函数式编程中最著名的功能是惰性求值和无限列表。在Python中,通常使用生成器来实现这些功能。但是函数式编程的戒律之一是不变性,而生成器也不是不变的。恰好相反。每当有人在生成器上调用next()
时,它都会更改其内部状态。
一种可能的解决方法是在调用生成器之前next()
复制一个生成器。这适用于某些生成器,例如count()
。 (也许count()
不是生成器?)
from itertools import count
count_gen = count()
count_gen_copy = copy(count_gen)
print(next(count_gen),next(count_gen),next(count_gen)) # => 0 1 2
print(next(count_gen_copy),next(count_gen_copy),next(count_gen_copy)) # => 0 1 2
但是,如果我定义了自己的生成器,例如my_count()
,则无法复制它。
def my_count(n=0):
while True:
yield n
n += 1
my_count_gen = my_count()
my_count_gen_copy = copy(my_count_gen)
print(next(my_count_gen),next(my_count_gen),next(my_count_gen))
print(next(my_count_gen_copy),next(my_count_gen_copy),next(my_count_gen_copy))
当我尝试执行copy(my_count_gen)
:TypeError: can't pickle generator objects
时收到错误消息。
有没有解决的办法,或者还有其他办法?
也许另一种询问方式是:copy()
在复制copy_gen
时复制的内容是什么?
谢谢。
P.S。如果我使用__iter__()
而不是copy()
,则__iter__()
的版本会像原始版本一样。
my_count_gen = my_count()
my_count_gen_i = my_count_gen.__iter__()
print(next(my_count_gen),next(my_count_gen)) # => 0 1 2
print(next(my_count_gen_i),next(my_count_gen_i),next(my_count_gen_i)) # => 3 4 5
解决方法
无法在Python中复制任意生成器。该操作只是没有意义。生成器可能依赖于其他各种不可复制的资源,例如文件句柄,数据库连接,锁,工作进程等。如果生成器持有并复制了锁,那么该锁会发生什么?如果生成器在数据库事务中,并且您将其复制,那么事务将如何处理?
您认为可复制的生成器根本不是生成器。它们是其他迭代器类的实例。如果要编写自己的迭代器类,则可以:
class MyCount:
def __init__(self,n=0):
self._n = n
def __iter__(self):
return self
def __next__(self):
retval = self._n
self._n += 1
return retval
您以这种方式编写的某些迭代器甚至可以合理地复制。其他人,copy.copy
会做的事情完全不合理且毫无用处。
尽管copy
在生成器上没有意义,但是您可以有效地“复制”一个迭代器,以便可以对其进行多次迭代。最简单的方法是使用itertools模块中的tee。
def my_count(n=0):
while True:
yield n
n += 1
a,b,c = itertools.tee(my_count(),3)
# now use a,c ...
这将使用内存来缓存迭代器的结果并将其传递。