有两种方法可以阅读您的问题。第一个很简单:您如何使用 Stream 构造,也许是您的第二个 link 中的构造,但具有递归定义?可以这样做,虽然在 Python 中有点笨拙。
在 Python 中,您可以表示循环数据结构,但不能直接表示。你不能写:
l = [l]
但你可以写:
l = [None]
l[0] = l
同样你不能写:
def integral(integrand,initial_value,dt):
int_rec = cons_stream(initial_value,
add_streams(scale_stream(integrand,dt),
int_rec))
return int_rec
但你可以写:
def integral(integrand,initial_value,dt):
placeholder = Stream(initial_value,lambda : None)
int_rec = cons_stream(initial_value,
add_streams(scale_stream(integrand,dt),
placeholder))
placeholder._compute_rest = lambda:int_rec
return int_rec
请注意,我们需要笨拙地预先计算placeholder 的第一个元素,然后只为流的其余部分修复递归。但这确实有效(以及所有其余代码的适当定义 - 我将把它全部放在这个答案的底部)。
但是,您问题的第二部分似乎是在询问如何在 Python 中自然地执行此操作。您要求“Python 中的类似 stream-y 构造”。显然,这个问题的答案正是生成器。生成器自然地提供了流概念的惰性求值。它的不同之处在于它不是自然递归地表达的,但是 Python 不支持它以及 Scheme,正如我们将看到的那样。
换句话说,严格的流概念可以用 Python 来表达(如链接和上面的),但惯用的方法是使用生成器。
通过流到生成器的一种直接机械转换,或多或少可以复制Scheme示例(但避免内置int):
def integral_rec(integrand,initial_value,dt):
def int_rec():
for x in cons_stream(initial_value,
add_streams(scale_stream(integrand,dt),int_rec())):
yield x
for x in int_rec():
yield x
def cons_stream(a,b):
yield a
for x in b:
yield x
def add_streams(a,b):
while True:
yield next(a) + next(b)
def scale_stream(a,b):
for x in a:
yield x * b
这里唯一棘手的事情是意识到您需要急切地调用递归使用int_rec 作为add_streams 的参数。调用它并不会开始它产生值 - 它只是创建准备好在需要时懒惰地产生它们的生成器。
这对于小型被积函数非常有效,尽管它不是很 Python。 Scheme 版本通过优化尾递归来工作 - 如果您的被积函数太长,Python 版本将超过最大堆栈深度。所以这在 Python 中并不合适。
我认为直接和自然的 pythonic 版本看起来像这样:
def integral(integrand,initial_value,dt):
value = initial_value
yield value
for x in integrand:
value += dt * x
yield value
这有效且正确地将integrand 懒惰地视为“流”。但是,它使用迭代而不是递归来解包被积函数迭代,这更像是 Python 方式。
在迁移到自然 Python 时,我还删除了流组合函数 - 例如,将 add_streams 替换为 +=。但如果我们想要一种中途版本,我们仍然可以使用它们:
def accum(initial_value,a):
value = initial_value
yield value
for x in a:
value += x
yield value
def integral_hybrid(integrand,initial_value,dt):
for x in accum(initial_value,scale_stream(integrand,dt)):
yield x
此混合版本使用 Scheme 中的流组合,并且仅避免尾递归。这仍然是 pythonic 并且 python 在 itertools 模块中包含了各种其他使用迭代的好方法。正如你所问,他们都“尊重流的懒惰性格”。
最后是第一个递归流示例的所有代码,其中大部分来自伯克利参考:
class Stream(object):
"""A lazily computed recursive list."""
def __init__(self, first, compute_rest, empty=False):
self.first = first
self._compute_rest = compute_rest
self.empty = empty
self._rest = None
self._computed = False
@property
def rest(self):
"""Return the rest of the stream, computing it if necessary."""
assert not self.empty, 'Empty streams have no rest.'
if not self._computed:
self._rest = self._compute_rest()
self._computed = True
return self._rest
def __repr__(self):
if self.empty:
return '<empty stream>'
return 'Stream({0}, <compute_rest>)'.format(repr(self.first))
Stream.empty = Stream(None, None, True)
def cons_stream(a,b):
return Stream(a,lambda : b)
def add_streams(a,b):
if a.empty or b.empty:
return Stream.empty
def compute_rest():
return add_streams(a.rest,b.rest)
return Stream(a.first+b.first,compute_rest)
def scale_stream(a,scale):
if a.empty:
return Stream.empty
def compute_rest():
return scale_stream(a.rest,scale)
return Stream(a.first*scale,compute_rest)
def make_integer_stream(first=1):
def compute_rest():
return make_integer_stream(first+1)
return Stream(first, compute_rest)
def truncate_stream(s, k):
if s.empty or k == 0:
return Stream.empty
def compute_rest():
return truncate_stream(s.rest, k-1)
return Stream(s.first, compute_rest)
def stream_to_list(s):
r = []
while not s.empty:
r.append(s.first)
s = s.rest
return r
def integral(integrand,initial_value,dt):
placeholder = Stream(initial_value,lambda : None)
int_rec = cons_stream(initial_value,
add_streams(scale_stream(integrand,dt),
placeholder))
placeholder._compute_rest = lambda:int_rec
return int_rec
a = truncate_stream(make_integer_stream(),5)
print(stream_to_list(integral(a,8,.5)))