Lambdify在Python中可以正常工作,但在Cython中会抛出异常。

3
我的网站运行这个Python脚本,如果使用Cython会更加优化。最近我需要添加Sympy with Lambdify,但是这与Cython不兼容。
因此,我将问题简化为一个最小工作示例。在代码中,我有一个带有字符串键和值为列表的字典。我想使用这些键作为变量。在下面的简化示例中,只有一个变量,但通常我需要更多。请查看以下示例:
import numpy as np
from sympy.parsing.sympy_parser import parse_expr
from sympy.utilities.lambdify import lambdify, implemented_function
from sympy import S, Symbol
from sympy.utilities.autowrap import ufuncify


def CreateMagneticFieldsList(dataToSave,equationString,DSList):

    expression  = S(equationString)
    numOfElements = len(dataToSave["MagneticFields"])

    #initialize the magnetic field output array
    magFieldsArray    = np.empty(numOfElements)
    magFieldsArray[:] = np.NaN

    lam_f = lambdify(tuple(DSList),expression,modules='numpy')
    try:
        # pass
        for i in range(numOfElements):
            replacementList = np.zeros(len(DSList))


            for j in range(len(DSList)):
                replacementList[j] = dataToSave[DSList[j]][i]

            try:
                val = np.double(lam_f(*replacementList))

            except:
                val = np.nan
            magFieldsArray[i] = val
    except:
        print("Error while evaluating the magnetic field expression")
    return magFieldsArray


list={"MagneticFields":[1,2,3,4,5]}

out=CreateMagneticFieldsList(list,"MagneticFields*5.1",["MagneticFields"])

print(out)

我们将其称为test.py。这个很好用。现在我想将其转化为cython,所以我使用以下脚本:

#!/bin/bash

cython --embed -o test.c test.py
gcc -pthread -fPIC -fwrapv -Ofast -Wall -L/lib/x86_64-linux-gnu/ -lpython3.4m -I/usr/include/python3.4 -o test.exe test.c

现在,如果我执行./test.exe,它会抛出一个异常!以下是异常信息:
Traceback (most recent call last):
  File "test.py", line 42, in init test (test.c:1811)
    out=CreateMagneticFieldsList(list,"MagneticFields*5.1",["MagneticFields"])
  File "test.py", line 19, in test.CreateMagneticFieldsList (test.c:1036)
    lam_f = lambdify(tuple(DSList),expression,modules='numpy')
  File "/usr/local/lib/python3.4/dist-packages/sympy/utilities/lambdify.py", line 363, in lambdify
    callers_local_vars = inspect.currentframe().f_back.f_locals.items()
AttributeError: 'NoneType' object has no attribute 'f_locals'

所以问题是:如何让lambdify与Cython一起工作?
注:我想指出我使用的是Debian Jessie,这就是为什么我使用Python 3.4。另外,我想指出,当不使用lambdify时,我没有任何关于Cython的问题。还要指出的是,通过“pip3 install cython --upgrade”将Cython更新到最新版本。

相关链接:https://groups.google.com/forum/#!topic/cython-users/9C6qSruI1QE - ivan_pozdeev
从我所看到的,--embed 并不是魔法,也不会进行优化(它只是从 libpython 调用解释器),因此几乎没有必要这样做。那么它的目的是什么? - ivan_pozdeev
1
@ivan_pozdeev 嗯,它比原始的Python快得多...你建议去掉 --embed 吗? - The Quantum Physicist
也许相关的是,cython文档-限制“目前我们生成虚假的回溯作为异常传播的一部分,但不填充本地变量并且无法填充co_code。” - J.J. Hakala
@J.J.Hakala将其作为答案发布,因为这很可能是正确的。 - ivan_pozdeev
显示剩余2条评论
2个回答

4
这是解决实际问题的一种折中方法(在评论和@jjhakala的回答中确定),即Cython不会为已编译函数生成完整的跟踪/内省信息。我从您的评论中了解到,由于速度原因,您希望保留大部分程序使用Cython编译。
“解决方案”是仅针对需要调用lambdify的单个函数使用Python解释器,其余功能保持在Cython中。您可以使用exec来实现这一点。
以下是此想法的一个非常简单的示例:
exec("""
def f(func_to_call):
    return func_to_call()""")

# a Cython compiled version    
def f2(func_to_call):
    return func_to_call())

这段代码可以被编译成Cython模块并且被导入,当被导入时,Python解释器将会执行字符串中的代码,并正确地将f添加到模块全局变量中。如果我们创建了一个纯Python函数

def g():
    return inspect.currentframe().f_back.f_locals

调用cython_module.f(g)会给我一个带有键func_to_call的字典(如预期),而调用cython_module.f2(g)会给我__main__模块的全局变量(但这是因为我正在从解释器中运行,而不是使用--embed)。

编辑: 基于您的代码的完整示例。
from sympy import S, lambdify # I'm assuming "S" comes from sympy
import numpy as np

CreateMagneticFieldsList = None # stops a compile error about CreateMagneticFieldsList being undefined

exec("""def CreateMagneticFieldsList(dataToSave,equationString,DSList):

    expression  = S(equationString)
    numOfElements = len(dataToSave["MagneticFields"])

    #initialize the magnetic field output array
    magFieldsArray    = np.empty(numOfElements)
    magFieldsArray[:] = np.NaN

    lam_f = lambdify(tuple(DSList),expression,modules='numpy')
    try:
        # pass
        for i in range(numOfElements):
            replacementList = np.zeros(len(DSList))


            for j in range(len(DSList)):
                replacementList[j] = dataToSave[DSList[j]][i]

            try:
                val = np.double(lam_f(*replacementList))

            except:
                val = np.nan
            magFieldsArray[i] = val
    except:
        print("Error while evaluating the magnetic field expression")
    return magFieldsArray""")


list={"MagneticFields":[1,2,3,4,5]}

out=CreateMagneticFieldsList(list,"MagneticFields*5.1",["MagneticFields"])
print(out)

当您使用您的脚本编译后,它会打印出以下内容:
[5.1 10.2 15.3 20.4 25.5]
我所做的主要工作是将您的函数包装在一个“exec”语句中,以便由Python解释器执行。这部分不会从Cython中获得任何好处,但是您的程序的其余部分仍然会获得好处。如果您想最大化使用Cython编译的数量,您可以将其分成多个函数,以便只有包含“lambdify”的小部分位于“exec”中。

请问您能否修改您的代码,使其与我提供的参数兼容?我对此还很陌生。我尝试将这些作为f的参数,但是出现了错误:fdata = f(CreateMagneticFieldsList,all_data,feqStr,all_symbols) TypeError: '_io.TextIOWrapper' object is not callable - The Quantum Physicist
啊,我觉得你有点误解了。你想在eval内定义CreateMagneticFieldsList,因为它是调用lambdify的函数。我只是给出了一个极其简化的例子来展示这个想法是可行的。我定义的f实际上是没有什么意义的。(如果还是有疑惑,我会尝试让我的代码更完整一些)。 - DavidW
我非常非常希望能得到一个可行的示例,最好是基于我在问题中提供的示例!我还向您保证,每个人都会喜欢它,因为这个问题相当普遍,而且没有人找到解决方法。 - The Quantum Physicist

2
Cython文档-限制中指出:

堆栈帧

目前,我们生成虚假的回溯作为异常传播的一部分,但不填充本地变量并且无法填充co_code。要完全兼容,我们必须在函数调用时生成这些堆栈帧对象(可能会有性能损失)。我们可以有一个选项来启用此功能进行调试。

f_locals

AttributeError: 'NoneType' object has no attribute 'f_locals'

似乎指向了这个不兼容的问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接