多态异常处理:如何捕获子类异常?

24

我有以下两个简单的C++异常层次结构:

class LIB_EXP ClusterException : public std::exception {
public:
    ClusterException() { }
    ClusterException(const std::string& what) { init(what); }
    virtual const char* what() const throw() { return what_.c_str(); }
    virtual ~ClusterException() throw() {}
    virtual ClusterException* clone() { return new ClusterException(*this);  } 
protected:
    void init(const std::string& what) { what_ = what; }
private:
    std::string what_;
};

class LIB_EXP ClusterExecutionException : public ClusterException {
public:
    ClusterExecutionException(const std::string& jsonResponse);
    std::string getErrorType() const throw() { return errorType_; }
    std::string getClusterResponse() const throw() { return clusterResponse_; }
    virtual ~ClusterExecutionException() throw() {}
    virtual ClusterExecutionException* clone() { return new ClusterExecutionException(*this);  } 
private:
    std::string errorType_;
    std::string clusterResponse_;
};

接下来我将它们使用Boost-Python导出到Python。请注意,我使用bases参数确保继承关系在翻译时得以保留:

class_<ClusterException> clusterException("ClusterException", no_init);
clusterException.add_property("message", &ClusterException::what);
clusterExceptionType = clusterException.ptr();
register_exception_translator<ClusterException>(&translateClusterException);

class_<ClusterExecutionException, bases<ClusterException> > clusterExecutionException("ClusterExecutionException", no_init);
clusterExecutionException.add_property("message", &ClusterExecutionException::what)
                         .add_property("errorType", &ClusterExecutionException::getErrorType)
                         .add_property("clusterResponse", &ClusterExecutionException::getClusterResponse);
clusterExecutionExceptionType = clusterExecutionException.ptr();
register_exception_translator<ClusterExecutionException>(&translateClusterExecutionException);

然后是异常翻译方法:
static PyObject *clusterExceptionType = NULL;
static void translateClusterException(ClusterException const &exception) {
  assert(clusterExceptionType != NULL); 
  boost::python::object pythonExceptionInstance(exception);
  PyErr_SetObject(clusterExceptionType, pythonExceptionInstance.ptr());
}

static PyObject *clusterExecutionExceptionType = NULL;
static void translateClusterExecutionException(ClusterExecutionException const &exception) {
  assert(clusterExecutionExceptionType != NULL);
  boost::python::object pythonExceptionInstance(exception);
  PyErr_SetObject(clusterExecutionExceptionType, pythonExceptionInstance.ptr());
}

我创建了以下测试C++函数,它会抛出异常:
static void boomTest(int exCase) {
  switch (exCase) {
    case 0:  throw ClusterException("Connection to server failed");
             break;
    case 1:  throw ClusterExecutionException("Error X while executing in the cluster");
             break;
    default: throw std::runtime_error("Unknown exception type");
  }
}

最后,这是调用我 C++ boomTest 的 Python 测试代码:

import cluster
reload(cluster)
from cluster import ClusterException, ClusterExecutionException

def test_exception(exCase):
    try:
        cluster.boomTest(exCase)

    except ClusterException as ex:
        print 'Success! ClusterException gracefully handled:' \
            '\n message="%s"' % ex.message
    except ClusterExecutionException as ex:
        print 'Success! ClusterExecutionException gracefully handled:' \
            '\n message="%s"' \
            '\n errorType="%s"' \
            '\n clusterResponse="%s"' % (ex.message, ex.errorType, ex.clusterResponse)
    except:
        print 'Caught unknown exception: %s "%s"' % (sys.exc_info()[0], sys.exc_info()[1])

def main():
    print '\n************************ throwing ClusterException ***********************************************************************'
    test_exception(0)
    print '\n************************ throwing ClusterExecutionException **************************************************************'
    test_exception(1)
    print '\n************************ throwing std::runtime_error *********************************************************************'
    test_exception(2)

if __name__ == "__main__":
    main()

到这里为止,一切都正常工作。但是,如果我从Python中删除ClusterExecutionException catch处理程序,则此异常将被捕获并回退到未知异常,而不是被捕获为其基本ClusterException

我在Boost-Python中尝试注册ClusterExecutionException的异常转换时,将其注册为其基本ClusterException,然后它被“多态”地捕获,但然后它就不会被当作ClusterExecutionException捕获。如何使ClusterExecutionException既能作为ClusterException又能作为ClusterExecutionException被捕获?我当然已经尝试过将此ClusterExecutionException异常注册为ClusterExceptionClusterExecutionException,但它遵循最后一个胜出策略,只有一个起作用,而不是两个都起作用。

还有其他解决方法吗?

更新1:此问题的全部问题是要在C++端找到except ClusterException as ex:类型的except Python语句的类型,即一旦进入C++端就不知道静态类型。由Boost.Python翻译的异常将调用与异常的动态类型相对应的翻译函数,而Python catch静态类型是未知的。

更新2:如建议更改Python代码为以下内容,即添加print(type(ex).__bases__)

def test_exception(exCase):
    try:
        cluster.boomTest(exCase)

    except ClusterException as ex:
        print(type(ex).__bases__)
        print 'Success! ClusterException gracefully handled:' \
            '\n message="%s"' % ex.message
    except ClusterExecutionException as ex:
        print(type(ex).__bases__)
        print 'Success! ClusterExecutionException gracefully handled:' \
            '\n message="%s"' \
            '\n errorType="%s"' \
            '\n clusterResponse="%s"' % (ex.message, ex.errorType, ex.clusterResponse)
    except:
        print 'Caught unknown exception: %s "%s"' % (sys.exc_info()[0], sys.exc_info()[1])

输出结果:

************************ throwing ClusterException ***********************************************************************
(<type 'Boost.Python.instance'>,)
Success! ClusterException gracefully handled:
 message="Connection to server failed"

************************ throwing ClusterExecutionException **************************************************************
(<class 'cluster.ClusterException'>,)
Success! ClusterExecutionException gracefully handled:
 message="Error X while executing in the cluster"
 errorType="LifeCycleException"
 clusterResponse="{ "resultStatus": "Error", "errorType": "LifeCycleException", "errorMessage": "Error X while executing in the cluster" }"

意思是继承关系从Python中“看到”。但是多态处理仍然不起作用。 更新3 这是运行VS dumpbin.exe的输出:
我使用的命令是:
dumpbin.exe /EXPORTS /SYMBOLS C:\ClusterDK\x64\Debug\ClusterDK.dll > c:\temp\dumpbin.out

并且输出的相关部分:

Microsoft (R) COFF/PE Dumper Version 11.00.50727.1
Copyright (C) Microsoft Corporation.  All rights reserved.

Dump of file C:\ClusterDK\x64\Debug\ClusterDK.dll

File Type: DLL

Section contains the following exports for ClusterDK.dll

00000000 characteristics
5A1689DA time date stamp Thu Nov 23 09:42:02 2017
    0.00 version
       1 ordinal base
      78 number of functions
      78 number of names

ordinal hint RVA      name

      8    7 00004485 ??0ClusterException@cluster@@QEAA@AEBV01@@Z = @ILT+13440(??0ClusterException@cluster@@QEAA@AEBV01@@Z)
      9    8 00001659 ??0ClusterException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+1620(??0ClusterException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)
     10    9 00001F1E ??0ClusterException@cluster@@QEAA@XZ = @ILT+3865(??0ClusterException@cluster@@QEAA@XZ)
     11    A 00004D4F ??0ClusterExecutionException@cluster@@QEAA@AEBV01@@Z = @ILT+15690(??0ClusterExecutionException@cluster@@QEAA@AEBV01@@Z)
     12    B 000010AA ??0ClusterExecutionException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+165(??0ClusterExecutionException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)
     27   1A 000035D0 ??1ClusterException@cluster@@UEAA@XZ = @ILT+9675(??1ClusterException@cluster@@UEAA@XZ)
     28   1B 00003C7E ??1ClusterExecutionException@cluster@@UEAA@XZ = @ILT+11385(??1ClusterExecutionException@cluster@@UEAA@XZ)
     37   24 00002BD5 ??4ClusterException@cluster@@QEAAAEAV01@AEBV01@@Z = @ILT+7120(??4ClusterException@cluster@@QEAAAEAV01@AEBV01@@Z)
     38   25 000034D1 ??4ClusterExecutionException@cluster@@QEAAAEAV01@AEBV01@@Z = @ILT+9420(??4ClusterExecutionException@cluster@@QEAAAEAV01@AEBV01@@Z)
     46   2D 000D2220 ??_7ClusterException@cluster@@6B@ = ??_7ClusterException@cluster@@6B@ (const cluster::ClusterException::`vftable')
     47   2E 000D2248 ??_7ClusterExecutionException@cluster@@6B@ = ??_7ClusterExecutionException@cluster@@6B@ (const cluster::ClusterExecutionException::`vftable')
     52   33 00004BB5 ?clone@ClusterException@cluster@@UEAAPEAV12@XZ = @ILT+15280(?clone@ClusterException@cluster@@UEAAPEAV12@XZ)
     53   34 00004D31 ?clone@ClusterExecutionException@cluster@@UEAAPEAV12@XZ = @ILT+15660(?clone@ClusterExecutionException@cluster@@UEAAPEAV12@XZ)
     61   3C 00001D43 ?getErrorType@ClusterExecutionException@cluster@@QEBA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ = @ILT+3390(?getErrorType@ClusterExecutionException@cluster@@QEBA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ)
     69   44 0000480E ?init@ClusterException@cluster@@IEAAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+14345(?init@ClusterException@cluster@@IEAAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)
     78   4D 000032FB ?what@ClusterException@cluster@@UEBAPEBDXZ = @ILT+8950(?what@ClusterException@cluster@@UEBAPEBDXZ)

Summary

    4000 .data
    5000 .idata
   12000 .pdata
   54000 .rdata
    2000 .reloc
    1000 .rsrc
   C9000 .text
    1000 .tls

1
当注册两者时,您是否还为基类类型重载了translateClusterExecutionException(..)方法?您可能需要将&ref dynamic_cast <>()转换为派生指针类型,以再次获得正确的行为。 - StarShine
1
当您删除以下行时: except ClusterExecutionException as ex: ... 您能验证一下所获得的异常的基类吗? 例如: except Exception as e: print(type(e).__bases__) - Juanjo Martin
@JuanjoMartin 谢谢!已完成,在 OP 中创建了 update2,它正确显示了基类。 - SkyWalker
由于ClusterExecutionException的基类是cluster.ClusterException,您是否尝试捕获异常except cluster.ClusterException as ex: - Juanjo Martin
这是 Boost.Python 的一个 bug 吗? - SkyWalker
显示剩余7条评论
2个回答

1
我不知道你的C++代码。但是你的Python代码有一个小问题。在ClusterException之前捕获ClusterExecutionException。应该始终将子异常处理程序放在基本异常之前。
test_exception中,如果引发了ClusterExecutionException,它将被ClusterException捕获,而无法到达ClusterExecutionException
代码应该如下所示:
def test_exception(exCase):
    try:
        cluster.boomTest(exCase)

    except ClusterExecutionException as ex:
        print 'Success! ClusterExecutionException gracefully handled:' \
            '\n message="%s"' \
            '\n errorType="%s"' \
            '\n clusterResponse="%s"' % (ex.message, ex.errorType, ex.clusterResponse)
    except ClusterException as ex:
        print 'Success! ClusterException gracefully handled:' \
            '\n message="%s"' % ex.message
    except:
        print 'Caught unknown exception: %s "%s"' % (sys.exc_info()[0], sys.exc_info()[1])

现在在Boost-Python中注册ClusterExecutionException的异常翻译时,我已尝试将其注册为其基类ClusterException,这是你在问题中提到的内容。请注意保留html标签。

谢谢你,但我认为这应该是一个注释而不是一个答案。异常处理中的顺序并不是通常的顺序,这也说明了我在 OP 中描述的问题。如果问题就是你回答的那样,我会一直处理ClusterException,因为它会多态地落入基类,但它行不通,这就是 OP。 - SkyWalker
我把它放在回答中,因为我没有评论的特权。 - surya singh
啊,好的,明白了 :) - SkyWalker
这个回答加上获得100分是完全误导的...这个回答只是指出了我知道的最佳实践,而没有解决OP的问题。访问这个问题的人会浪费时间,误以为解决OP的方法只是重新排列异常处理。另一个答案更值得获得100分并置于首位。 - SkyWalker

1

我的Python技能有些生疏,而且我还没有测试过这个,所以可能需要进一步改进,但是尝试添加异常翻译方法来处理基类异常类型:

static PyObject *clusterExecutionAsClusterExceptionType = NULL;
static void translateClusterExecutionAsClusterException(ClusterException const &exception) {

  ClusterExecutionException* upcasted = dynamic_cast<ClusterExecutionException*>(&exception);
  if (upcasted)
  {
    assert(clusterExecutionAsClusterExceptionType != NULL);
    boost::python::object pythonExceptionInstance(*upcasted);
  PyErr_SetObject(clusterExecutionAsClusterExceptionType, pythonExceptionInstance.ptr());
  }
}

register_exception_translator<ClusterException>(&translateClusterExecutionAsClusterException);

谢谢,我已经尝试了你发布的内容,但它不起作用。我还尝试了多个派生版本,但都没有成功。这个问题的关键是异常类型映射,并使其与在 except (catch) 语句中未知于 C++ 的 Python 异常类型匹配... - SkyWalker
1
已经有一段时间了,但我突然想到了一些事情...如果您能够从包含函数/对象签名的Python DLL中转储所有导出符号(dumpbin.exe),则可以拼凑出所需的签名以触发异常。 - StarShine
好的,我更新了问题。如果我们找到了解决方案,我会很高兴接受你的答案并请求将100分转移到你的账户上。 - SkyWalker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接