【问题标题】:Python equivalent of Mathematica's Sow/ReapPython 相当于 Mathematica 的 Sow/Reap
【发布时间】:2013-09-16 00:05:19
【问题描述】:

假设我在 Mathematica 中定义了以下函数:

f[list_] := Map[Prime[Sow[#]] &, list];

它输出一个素数列表,这样如果输入列表在位置 i 有 n,那么输出列表将在位置 i 包含第 n 个素数。例如,

In[2]:= f[{1, 3, 4}]

Out[2]= {2, 5, 7}

现在,如果出于某种原因(调试等),我想检查将哪些值输入Prime 函数。因为函数里有Sow命令,我可以做

In[3] := Reap[f[{1, 3, 4}]]

Out[3] := {{2, 5, 7}, {{1, 3, 4}}}

有关播种/收割的更多详细信息,请参阅Wolfram Documentation。我的问题是,是否存在与 Mathematica 的 Sow and Reap 功能等效的自然 Python? 特别是,有没有一种方法可以在不从您想要执行的 python 函数中显式返回额外内容的情况下执行此类操作它是,编写第二个几乎相同但返回额外内容的第二个python函数,还是使用全局变量?

【问题讨论】:

  • 你不能用函数来做(不使用全局变量)。您可以做的是编写一个以某种方式存储中间结果的类,然后稍后再读取它们。不过,没有内置工具。
  • 顺便说一下,在这里使用全局变量是否是错误的并不完全清楚。如果我对这些文档的理解正确,Mathematica sow/reap 会操纵一种全局堆栈。
  • 是的,也许这是正确的思考方式。只要堆栈包含有关每只母猪被调用的函数的信息,就可以以这种方式对其进行建模。也就是说,Sow/Reap 操作的变量必须表现得好像它是每个函数调用的本地变量。
  • 它甚至需要跟踪吗?如果您从使用 sow 的函数中获得收益并且该函数调用另一个也使用 sow 的函数,我无法从 Mathematica 文档中得知会发生什么。这些值是全部堆积在一个收获列表中,还是以某种方式嵌套?
  • sow 将结果传递到最近的封闭 reap[].. 一个 reap 下的多头母猪会导致值列表(数组)的列表。我不认为这个问题真的需要实现所有的复杂性。

标签: python wolfram-mathematica


【解决方案1】:

我想出了两种方法来实现类似这样的基本版本,每种方法都有自己的局限性。这是第一个版本:

farm = []

def sower(func):
    def wrapped(*args, **kw):
        farm.append([])
        return func(*args, **kw)
    return wrapped

def sow(val):
    farm[-1].append(val)
    return val

def reap(val):
    return val, farm.pop()

您可以这样使用它(基于 Mathematica 文档页面中的一个示例):

>>> @sower
... def someSum():
...     return sum(sow(x**2 + 1) if (x**2 + 1) % 2 == 0 else x**2 + 1 for x in xrange(1, 11))
>>> someSum()
395
>>> reap(someSum())
(395, [2, 10, 26, 50, 82])

这有很多限制:

  1. 任何想要使用sow 的函数都必须使用sower 装饰器进行装饰。这意味着您不能像 Mathematica 示例那样在内联表达式内部使用 sow ,例如列表推导式。您也许可以通过检查调用堆栈来破解它,但它可能会变得很丑。
  2. 任何播种但未收获的价值都会永久存储在“农场”中,因此随着时间的推移,农场会变得越来越大。
  3. 它没有文档中显示的“标记”功能,尽管添加起来并不难。

写这篇文章让我想到了一个更简单的实现,但权衡略有不同:

farm = []

def sow(val):
    if farm:
        farm[-1].append(val)
    return val

def reap(expr):
    farm.append([])
    val = expr()
    return val, farm.pop()

这个你可以这样使用,它有点类似于 Mathematica 版本:

>>> reap(lambda: sum(sow(x**2 + 1) if (x**2 + 1) % 2 == 0 else x**2 + 1 for x in xrange(1, 11)))
(395, [2, 10, 26, 50, 82])

这个不需要装饰器,它会清理收获的值,但它需要一个无参数函数作为它的参数,这需要你将你的播种表达式包装在一个函数中(这里使用lambda 完成) .此外,这意味着被 reaped 表达式调用的任何函数中的所有播种值都将插入到同一个列表中,这可能会导致奇怪的排序;我无法从 Mathematica 文档中看出这是 Mathematica 所做的还是什么。

【讨论】:

    【解决方案2】:

    不幸的是,据我所知,Python 中没有“播种”和“收割”的简单或惯用等价物。但是,您可以使用生成器和装饰器的组合来伪造它,如下所示:

    def sow(func):
        class wrapper(object):
            def __call__(self, *args, **kwargs):
                output = list(func(*args, **kwargs))
                return output[-1]
    
            def reap(self, *args, **kwargs):
                output = list(func(*args, **kwargs))
                final = output[-1]
                intermediate = output[0:-1]
                return [final, intermediate]
    
        return wrapper()
    
    @sow    
    def f(seq, mul):
        yield seq
        yield mul
        yield [a * mul for a in seq]
    
    print f([1, 2, 3], 4)         # [4, 8, 12]
    print f.reap([1, 2, 3], 4)    # [[4, 8, 12], [[1, 2, 3], 4]]
    

    但是,与 Mathematica 相比,这种方法有一些限制。首先,必须重写该函数,使其使用yield 而不是return,将其变成一个生成器。最后产生的值将是最终输出。

    它也没有文档描述的类似“异常”的属性。装饰器@sow 只是返回一个伪装成函数的类,并添加了一个额外的参数reap


    另一种解决方案可能是尝试使用macropy。由于它直接操纵 Python 的 AST 和字节码,因此您可能能够直接支持更符合您正在寻找的东西。 tracing 宏看起来与您想要的内容有点相似。

    【讨论】:

    • 谢谢。令人失望的是,没有一种简单、自然的方法可以做到这一点,但我对您提供给 macropy 的参考非常感兴趣。跟踪功能确实符合我的要求,尽管我怀疑它会在我使用它的情况下产生大量额外信息。
    • 这对于像 Mathematica 示例这样的东西来说会很尴尬,因为每次迭代都会播下一个值(例如,sum(sow(x**2) for x in seq))。您的计算函数必须 yield 迭代中的每个值,同时将它们累积到列表中,然后以某种方式重新用于进行实际计算。
    猜你喜欢
    • 2014-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-17
    • 2016-05-08
    • 2011-06-05
    • 1970-01-01
    • 2010-10-29
    相关资源
    最近更新 更多