python - 使用cython从C++构造函数传播异常

标签 python c++ exception cython

如果某些参数无效,我想在C++类构造函数中引发异常,例如,PyErr_SetString(PyExc_ValueError, "Error occurred")。不幸的是,它不能正确传播,我得到了SystemError: <class 'testlib.foo.Foo'> returned a result with an error set。甚至有可能使其在构造函​​数中工作?
几个问题:

  • 所有逻辑都应该在C++类中,而不必扩展到.pyx文件中。
  • 验证应在构造函数内部进行,因此在对象创建后不需要调用单独的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'
        )
    )
    
    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.cpp
    #include "c_foo.h"
    
    
    namespace foo {
    
        Foo::Foo() {
            PyErr_SetString(PyExc_ValueError, "Error occurred");
        }
        Foo::~Foo() {}
    }
    
    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_foo.py
    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
    
    可以通过将throw std::invalid_argument("Some error")Foo() except +中的c_foo.pxd一起执行来解决前两个问题,但不适用于任意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++构造函数上的except *可能是一个小错误……)
    # distutils: language=c++
    
    cdef extern from *:
        """
        class Foo {
            public:
                Foo() {
                    PyErr_SetString(PyExc_ValueError, "Error occurred");
                }
                ~Foo() {}
                
                static Foo getFoo() {
                    return Foo();
                }
        };
        """
        cdef cppclass Foo:
            Foo()
            @staticmethod
            Foo getFoo() except *
    
    def f():
        Foo.getFoo()
    
    此代码从Cython生成以下C++代码:
    Foo::getFoo(); if (unlikely(PyErr_Occurred())) __PYX_ERR(0, 23, __pyx_L1_error)
    
    从而实现您的期望-它检查C++异常并按预期方式处理它。

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

    关于python - 使用cython从C++构造函数传播异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64773494/

    相关文章:

    python - 引用函数还是创建一个只调用另一个的新函数?

    python - Pandas 不会重命名多索引列名

    python - 无法从 DOS 提示符启动 IronPython 解释器

    c# - 如何将kinect骨架数据保存到文件中?

    PYTHON如何将字符串转换为int或float

    c++ - Visual Studio 2012/2013 类内成员初始化

    C++ - 不同类的方法重载

    windows - 如何在向量异常处理程序中处理 0x40010006 异常?

    c# - 异常没有被捕获 block

    C# "Using"语法