Python 从C+传播异常+;带cython的构造函数

Python 从C+传播异常+;带cython的构造函数,python,c++,exception,cython,Python,C++,Exception,Cython,我想在某些参数无效的情况下,在C++类构造函数中引发一个异常,例如,do>代码> PyrRyStScript(PyExcValueError,“错误发生”)< /C>。不幸的是,它不能正确传播,我得到了SystemError:返回了一个错误集为的结果。甚至可以让它在构造函数中工作吗 若干关切: 所有的逻辑应该在C++类中,而不扩展到 .Pyx文件。 验证应该在构造函数内部进行,这样就不需要在创建对象后调用separateinit()方法 不仅可以提出一些标准的例外情况,还可以提出任何自定义的例

我想在某些参数无效的情况下,在C++类构造函数中引发一个异常,例如,do>代码> PyrRyStScript(PyExcValueError,“错误发生”)< /C>。不幸的是,它不能正确传播,我得到了
SystemError:返回了一个错误集为
的结果。甚至可以让它在构造函数中工作吗

若干关切:

    所有的逻辑应该在C++类中,而不扩展到<代码> .Pyx文件。
  • 验证应该在构造函数内部进行,这样就不需要在创建对象后调用separate
    init()
    方法
  • 不仅可以提出一些标准的例外情况,还可以提出任何自定义的例外情况
setup.py

from Cython.Build import cythonize
from Cython.Distutils import Extension
from setuptools import find_packages, setup

setup(
    name='testlib',
    python_requires='~=3.7.0',
    packages=find_packages('src'),
    package_dir={'': 'src'},
    ext_modules=cythonize(
        [
            Extension(
                'testlib.foo',
                ['src/testlib/foo.pyx', 'src/testlib/c_foo.cpp'],
                extra_compile_args=['-std=c++11'], language='c++',
            )
        ], language_level='3'
    )
)
import pytest
from testlib.foo import Foo


def test_foo():
    with pytest.raises(ValueError):
        Foo()    # SystemError: <class 'testlib.foo.Foo'> returned a result with an error set
src/testlib/c_foo.h

#ifndef FOO_H
#define FOO_H
#define PY_SSIZE_T_CLEAN

#include <Python.h>

namespace foo {
    class Foo {
        public:
            Foo();
            ~Foo();
    };
}

#endif
src/testlib/c_foo.pxd

cdef extern from "c_foo.h" namespace "foo":
    cdef cppclass Foo:
        Foo()
src/testlib/foo.pyx

from .c_foo cimport Foo as CFoo

cdef class Foo:
    cdef CFoo *c_foo

    def __cinit__(self):
        self.c_foo = new CFoo()

    def __dealloc__(self):
        del self.c_foo
测试/test\u foo.py

from Cython.Build import cythonize
from Cython.Distutils import Extension
from setuptools import find_packages, setup

setup(
    name='testlib',
    python_requires='~=3.7.0',
    packages=find_packages('src'),
    package_dir={'': 'src'},
    ext_modules=cythonize(
        [
            Extension(
                'testlib.foo',
                ['src/testlib/foo.pyx', 'src/testlib/c_foo.cpp'],
                extra_compile_args=['-std=c++11'], language='c++',
            )
        ], language_level='3'
    )
)
import pytest
from testlib.foo import Foo


def test_foo():
    with pytest.raises(ValueError):
        Foo()    # SystemError: <class 'testlib.foo.Foo'> returned a result with an error set
导入pytest
从testlib.foo导入foo
def test_foo():
使用pytest.raises(ValueError):
Foo()#SystemError:返回了一个带有错误集的结果

前两个问题可以通过在
c\u Foo.pxd
中执行
throw std::invalid_argument(“Some error”)
Foo()except+
来解决,但它不适用于任意python异常。

首先,请注意,这可能会出现严重错误。Python作用域规则与C++的范围规则不同,Cython主要遵循Python范围规则。考虑

# distutils: language=c++

cdef extern from "something.hpp":
    cdef cppclass C:
        C()

def f(x):
    cdef C c
    if x>0:
        c = C()
c
必须可用于任何分支,因此默认在函数开始时初始化:

CYTHON_UNUSED C __pyx_v_c;

此默认初始化不会在其周围有标准异常处理(原则上它可能是像Python异常一样,但在构造函数中处理C++异常非常困难,并且在C++中编译代码)。 因此,如果您的类中有任何堆栈分配的变量,您就有可能将Cython置于异常设置的状态,但它认为不应该这样


话虽如此,我认为实现这一点的方法可能是使用静态工厂函数。Cython确实理解了这些异常规范(它不理解<代码>,除了C++ <代码> > C++构造函数可能是一个小bug…)

此代码生成Cython的以下C++代码:

Foo::getFoo(); if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 23, __pyx_L1_error)
,这样就可以做到你所期望的——它检查C++异常并按照预期处理它。

我仍然认为这是一个坏主意。

除了*
(这意味着可以设置任何Python异常,无法从返回类型中区分)之外,您是否尝试过
?但是,对于空C++构造函数可能有一些具体的问题。可能的方法是不够的(通过选择适当的C++异常,或者试图分配一个异常翻译函数)或将所有异常转换成PY边上的特定的一个。我试过了,它给出了函数返回Python对象时不允许使用的
异常子句,这听起来像是一个bug。复杂的是,任何一个分配了C++对象的堆栈都在Cython函数的开始时被初始化,在那里它不可能添加异常检查,所以很难用它来做很多有用的事情。除了*>代码>而不是构造函数,你可以用“代码>”查看静态工厂函数。