【问题标题】:Lambdify works with Python, but throws an exception with CythonLambdify 与 Python 一起工作,但在 Cython 中抛出异常
【发布时间】:2016-03-24 00:20:46
【问题描述】:

我的网站运行这个 Python 脚本,如果使用 Cython,它会更加优化。最近我需要添加Sympy with Lambdify,这与Cython 不太好。

所以我将问题简化为一个最小的工作示例。在代码中,我有一个带有字符串键的字典,其中的值是列表。我想将这些键用作变量。在下面的简化示例中,只有 1 个变量,但通常我需要更多。请检查以下示例:

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。这很好用。现在我想对它进行cythonize,所以我使用以下脚本:

#!/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 没有任何问题。另外我想指出,Cython 已更新到最新版本,pip3 install cython --upgrade

【问题讨论】:

  • 据我所见,--embed 不是魔术,也不会做优化(它只是从libpython 调用解释器),所以几乎不需要这样做。目的是什么?
  • @ivan_pozdeev 嗯,它比原始 Python 快得多...您是否建议删除 --embed
  • 可能相关,cython docs - limitations“目前我们生成虚假回溯作为异常传播的一部分,但不要填写本地,也无法填写 co_code。”
  • @J.J.Hakala 将此作为答案发布,因为它很可能是它。

标签: python optimization cython sympy lambdify


【解决方案1】:

这是 Cython 不会为编译函数生成完整回溯/内省信息的真正问题(在 cmets 和 @jjhakala 的答案中确定)的一种解决方法。我从您的 cmets 中得知,出于速度原因,您希望使用 Cython 编译大部分程序。

“解决方案”是仅将 Python 解释器用于需要调用 lambdify 的单个函数,并将其余的留在 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
  • 啊。我想你误解了一点。您想在eval 中定义CreateMagneticFieldsList,因为它是调用lambdify 的那个。我只是举了一个非常简化的例子来证明这个想法是有效的。 f 正如我所定义的那样,它真的毫无意义。 (如果这仍然令人困惑,我会尝试使我的代码更完整)。
  • 我真的非常感谢一个工作示例,最好基于我在问题中提供的示例!我也保证每个人都会喜欢它,因为这个问题很常见,没有人找到解决方案。
【解决方案2】:

cython docs - limitations 中表示

堆栈帧

目前我们生成虚假回溯作为异常的一部分 传播,但不要填写locals,也不能填写co_code。成为 完全兼容,我们必须生成这些堆栈帧对象 在函数调用时(具有潜在的性能损失)。我们可能会 可以选择启用它以进行调试。

f_locals

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-04
    • 1970-01-01
    • 2011-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-11
    • 1970-01-01
    相关资源
    最近更新 更多