【问题标题】:Dynamically rethrowing self-defined C++ exceptions as Python exceptions using SWIG使用 SWIG 将自定义 C++ 异常动态地重新抛出为 Python 异常
【发布时间】:2013-02-07 00:00:53
【问题描述】:

情况

我想使用 SWIG 为 C++ API 创建 Python 语言绑定。某些 API 函数可能会引发异常。 C++ 应用程序具有自定义异常的层次结构,如下例所示:

std::exception
  -> API::Exception
    -> API::NetworkException
      -> API::TimeoutException
      -> API::UnreachableException
    -> API::InvalidAddressException

期望的行为如下:

  1. 所有异常类型都应该有一个匹配的 Python 类作为 wrapper。这些包装类应该是有效的 Python 异常

  2. 当 API 调用引发 C++ 异常时,应该捕获对应的 Python 异常(即捕获的 C++ 异常的包装类)应该抛出

  3. 这应该是一个动态过程:Python 异常类型在运行时决定,仅基于捕获的 C++ 异常的运行时类型。这样,无需在 SWIG 接口文件中描述完整的异常层次结构。

问题和疑问

  1. 包装类没有 Python 例外。

    虽然 SWIG 为所有自定义异常(与任何其他类一样)创建包装类,但这些类不是 Python 异常。基本异常的包装器(示例中为API::Exception)扩展了Object 而不是BaseException,Python 中的所有异常都应派生自该Python 类。

    此外,让 SWIG 手动添加父类似乎是不可能的。请注意,通过使用 %typemap(javabase) 将 SWIG 与 Java 结合使用时,这是可能的(有关详细信息,请参阅 SWIG documentation)。

  2. Python C API如何抛出用户定义的异常?

    从 Python C API 引发 Python 异常的最常见方法是调用 PyErr_SetString [reference]。这也显示在下面的演示应用程序中。

    但这对于 Python 的标准(内置)异常来说只是微不足道的,因为对它们的引用存储在 Python C API 的全局变量 [reference] 中。

    我知道有一种方法 PyErr_NewException [reference] 可以获取对自定义异常的引用,但我没有得到这个工作。

  3. Python C API 如何在运行时评估 C++ 类型,然后通过名称找到对应的 Python 包装类?

    我假设可以在运行时通过 Python C API 的 reflection part 按名称搜索 Python 类。这是要走的路吗?在实践中是如何做到的?

演示应用程序

为了解决这个问题,我创建了一个微型 C++ API,其中包含一个计算数字阶乘的函数。它有一个最小的自定义异常层次结构,只包含一个类TooBigException

请注意,此异常是一般问题中的基本异常,应用程序应使用它的任何子类。这意味着解决方案只能使用捕获的异常的动态(即运行时)类型在 Python 中重新抛出它(见下文)。

演示应用的完整源代码如下:

// File: numbers.h
namespace numbers {
int fact(int n);
}

// File: numbers.cpp
#include "TooBigException.h"
namespace numbers {
int fact(int n) {
    if (n > 10) throw TooBigException("Value too big", n);
    else if (n <= 1) return 1;
    else return n*fact(n-1);
}
}

// File: TooBigException.h
namespace numbers {
class TooBigException: public std::exception {
public:
    explicit TooBigException(const std::string & inMessage,
                             const int inValue);
    virtual ~TooBigException() throw() {}
    virtual const char* what() const throw();
    const std::string & message() const;
    const int value() const;
private:
    std::string mMessage;
    int mValue;
};
}

// File: TooBigException.cpp
#include "TooBigException.h"
namespace numbers {
TooBigException::TooBigException(const std::string & inMessage, const int inValue):
    std::exception(),
    mMessage(inMessage),
    mValue(inValue)
{
}
const char* TooBigException::what() const throw(){
    return mMessage.c_str();
}
const std::string & TooBigException::message() const {
    return mMessage;
}
const int TooBigException::value() const {
    return mValue;
}
}

要获得 Python 绑定,我使用以下 SWIG 接口文件:

// File: numbers.i
%module numbers
%include "stl.i"
%include "exception.i"

%{
#define SWIG_FILE_WITH_INIT
#include "TooBigException.h"
#include "numbers.h"
%}

%exception {
    try {
        $action
    }
    catch (const numbers::TooBigException & e) {
        // This catches any self-defined exception in the exception hierarchy,
        // because they all derive from this base class. 
        <TODO>
    }
    catch (const std::exception & e)
    {
        SWIG_exception(SWIG_RuntimeError, (std::string("C++ std::exception: ") + e.what()).c_str());
    }
    catch (...)
    {
        SWIG_exception(SWIG_UnknownError, "C++ anonymous exception");
    }
}

%include "TooBigException.h"
%include "numbers.h"

因此,对 API 的每次调用都由 try-catch 块包装。我们的基本类型的第一个异常被捕获和处理。然后使用 SWIG 异常库捕获并重新抛出所有其他异常。

注意numbers::TooBigException 的任何子类都被捕获,应该抛出它们的动态(即运行时)类型的包装器,而不是它们的静态包装器(即编译time) 类型,始终为TooBigException!

在 Linux 机器上执行以下命令即可轻松构建项目:

$ swig -c++ -python numbers.i
$ g++ -fPIC -shared TooBigException.cpp numbers.cpp numbers_wrap.cxx \
    -I/usr/include/python2.7 -o _numbers.so

当前实施

我当前的实现仍然(成功地)抛出一个固定的标准 Python 异常。然后将上面的代码&lt;TODO&gt;替换为:

PyErr_SetString(PyExc_Exception, (std::string("C++ self-defined exception ") + e.what()).c_str());
return NULL;

这在 Python 中给出了以下(预期的)行为:

>>> import numbers
>>> fact(11)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: C++ self-defined exception Value too big

【问题讨论】:

  • 我不太确定你想要什么,但你有没有看过这段代码对你的第二种情况有帮助? throw;

标签: c++ python exception reflection swig


【解决方案1】:

层次结构示例

std::exception
  -> API::Exception
    -> API::NetworkException
      -> API::TimeoutException
      -> API::UnreachableException
    -> API::InvalidAddressException

example.i:

%module example
%include "stl.i"
%include "exception.i"

%{
#define SWIG_FILE_WITH_INIT
#include "example.cpp"
%}

%{

#define CATCH_PE(Namespace,Exception) \
    catch(const Namespace::Exception &e) \
    { \
       SWIG_Python_Raise(SWIG_NewPointerObj(new Namespace::Exception(e), \
            SWIGTYPE_p_##Namespace##__##Exception,SWIG_POINTER_OWN), \
            #Exception, SWIGTYPE_p_##Namespace##__##Exception); \
       SWIG_fail; \
    } \
/**/

// should be in "derived first" order
#define FOR_EACH_EXCEPTION(ACTION) \
   ACTION(API,UnreachableException) \
   ACTION(API,TimeoutException) \
   ACTION(API,InvalidAddressException) \
   ACTION(API,NetworkException) \
   ACTION(API,Exception) \
/**/
// In order to remove macros, need traits:
// http://swig.10945.n7.nabble.com/traits-based-access-to-swig-type-info-td12315.html
%}

%exception {
    try {
        $action
    }
    FOR_EACH_EXCEPTION(CATCH_PE)
    catch (const std::exception & e)
    {
        SWIG_exception(SWIG_RuntimeError, (std::string("C++ std::exception: ") + e.what()).c_str());
    }
    catch (...)
    {
        SWIG_exception(SWIG_UnknownError, "C++ anonymous exception");
    }
}

%include "example.cpp"

example.cpp:

#include <exception>
#include <stdexcept>

namespace API
{
    struct Exception: std::exception
    {
        virtual const char* what() const throw()
        {
            return "It is API::Exception";
        }
    };
    struct NetworkException: Exception
    {
        virtual const char* what() const throw()
        {
            return "It is API::NetworkException";
        }
    };
    struct TimeoutException: NetworkException
    {
        virtual const char* what() const throw()
        {
            return "It is API::TimeoutException";
        }
    };
    struct UnreachableException: NetworkException
    {
        virtual const char* what() const throw()
        {
            return "It is API::UnreachableException";
        }
    };
    struct InvalidAddressException: Exception
    {
        virtual const char* what() const throw()
        {
            return "It is API::InvalidAddressException";
        }
    };

    inline void select(int i)
    {
        switch(i)
        {
            case 0: throw Exception();
            case 1: throw NetworkException();
            case 2: throw TimeoutException();
            case 3: throw UnreachableException();
            case 4: throw InvalidAddressException();
            default: throw std::runtime_error("It is std::runtime_error");
        }
    }
}

构建:

swig -c++ -python example.i &&
g++ -fPIC -shared -lpython2.7 example.cpp example_wrap.cxx -I/usr/include/python2.7 -o _example.so

test.py:

#!/usr/bin/env python2.7

from exceptions import BaseException
from example import *

def catch(i):
    try:
        select(i)
    except UnreachableException as e:
        print "Caught UnreachableException"
        print e.what()
        print e
    except TimeoutException as e:
        print "Caught TimeoutException"
        print e.what()
        print e
    except InvalidAddressException as e:
        print "Caught InvalidAddressException"
        print e.what()
        print e
    except NetworkException as e:
        print "Caught NetworkException"
        print e.what()
        print e
    except Exception as e:
        print "Caught Exception"
        print e.what()
        print e
    except BaseException as e:
        print "Caught BaseException"
        print str(e)
    print "_"*16

for i in xrange(6):
    catch(i)

输出是:

Caught Exception
It is API::Exception
<example.Exception; proxy of <Swig Object of type 'API::Exception *' at 0x7f9f54a02120> >
________________
Caught NetworkException
It is API::NetworkException
<example.NetworkException; proxy of <Swig Object of type 'API::NetworkException *' at 0x7f9f54a02120> >
________________
Caught TimeoutException
It is API::TimeoutException
<example.TimeoutException; proxy of <Swig Object of type 'API::TimeoutException *' at 0x7f9f54a02120> >
________________
Caught UnreachableException
It is API::UnreachableException
<example.UnreachableException; proxy of <Swig Object of type 'API::UnreachableException *' at 0x7f9f54a02120> >
________________
Caught InvalidAddressException
It is API::InvalidAddressException
<example.InvalidAddressException; proxy of <Swig Object of type 'API::InvalidAddressException *' at 0x7f9f54a02120> >
________________
Caught BaseException
C++ std::exception: It is std::runtime_error
________________

基于answer in maillist

【讨论】:

    【解决方案2】:

    看起来有人已经在 swig-user 列表中回答了您的基本问题...

    %exception {
      try {
        $action
      } catch (MyException &_e) {
        SWIG_Python_Raise(SWIG_NewPointerObj(
                (new MyException(static_cast<const MyException& >(_e))),  
                SWIGTYPE_p_MyException,SWIG_POINTER_OWN),
            "MyException", SWIGTYPE_p_MyException); 
        SWIG_fail;
      } 
    }
    

    我相信,这确实假设您已经为异常类生成了包装器。

    【讨论】:

    • 这确实是答案的一部分。但是,正如问题中明确指出的那样,我想使用 dynamic 类型,而此解决方案进行静态转换。换句话说:MyException 的任何子类都将被捕获并更改为 MyException,并且所有子类信息都将丢失。
    猜你喜欢
    • 1970-01-01
    • 2014-10-14
    • 2020-02-18
    • 2011-05-30
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    相关资源
    最近更新 更多