使用SWIG动态地将自定义C ++异常重新抛出为Python异常

时间:2013-02-21 15:30:36

标签: c++ python exception reflection swig

场合

我想使用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接口文件中描述完整的异常层次结构。

  4. 问题和疑问

    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类。这是要走的路吗?它是如何在实践中完成的?

    4. 演示应用

      为了试验这个问题,我创建了一个带有单个函数的小型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的任何子类被捕获,其动态(即运行时)类型的 包装器应该被抛出 ,而不是它们的静态包装器(即编译时间)类型,始终为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异常。然后,上面的代码<TODO>将替换为:

      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
      

2 个答案:

答案 0 :(得分:9)

看起来有人在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;
  } 
}

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

答案 1 :(得分:5)

您的层次结构示例

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