1

私はC++でPythonのCAPI(2.7)を使用して、Pythonツリー構造をC++ツリーに変換しています。コードは次のようになります。

  • Pythonツリーは、子のリストを持つクラスとして再帰的に実装されます。リーフノードは単なるプリミティブ整数です(クラスインスタンスではありません)

  • モジュールをロードし、ここのコードを使用してC ++からpythonメソッドを呼び出します。これにより、ツリーのインスタンスpython_treeがC++のPyObjectとして返されます。

  • 取得したPyObjectを再帰的にトラバースします。子のリストを取得するには、次のようにします。

    PyObject* attr = PyString_FromString("children");
    PyObject* list = PyObject_GetAttr(python_tree,attr);
    for (int i=0; i<PyList_Size(list); i++) {
        PyObject* child = PyList_GetItem(list,i); 
        ...
    

非常に簡単で、PyObject_GetAttr(Objects / object.c:1193ですが、APIコードが表示されません)の呼び出しで、最終的にセグメンテーション違反が発生するまで機能します。これは、ツリーの最後のリーフノードへの訪問時に発生するようです。

問題を特定するのに苦労しています。C APIで再帰を行うための特別な考慮事項はありますか?Py_INCREF / Py_DECREFを使用する必要があるのか​​、これらの関数などを使用する必要があるのか​​わかりません。正直に言うと、APIがどのように機能するのか完全には理解していません。どんな助けでも大歓迎です!

編集:いくつかの最小限のコード:

void VisitTree(PyObject* py_tree) throw (Python_exception)
{
    PyObject* attr = PyString_FromString("children");
    if (PyObject_HasAttr(py_tree, attr)) // segfault on last visit
    {
        PyObject* list = PyObject_GetAttr(py_tree,attr);
        if (list)
        {
            int size = PyList_Size(list);
            for (int i=0; i<size; i++)
            {
                PyObject* py_child = PyList_GetItem(list,i);
                PyObject *cls = PyString_FromString("ExpressionTree");
                // check if child is class instance or number (terminal)
                if (PyInt_Check(py_child) || PyLong_Check(py_child) || PyString_Check(py_child)) 
                    ;// terminal - do nothing for now
                else if (PyObject_IsInstance(py_child, cls))
                    VisitTree(py_child);
                else
                    throw Python_exception("unrecognized object from python");
            }
        }
    }
}
4

1 に答える 1

6

Python/Cコードに関するいくつかの問題を特定できます。

  • PyObject_IsInstance2番目の引数として、文字列ではなくクラスを取ります。

  • 参照カウント専用のコードはありません。によって返されたものなどの新しい参照PyObject_GetAttrが解放されることはなく、で取得された借用された参照が使用前に取得されるPyList_GetItemことはありません。C++例外を他の点では純粋なPython/Cと混合すると、問題が悪化し、正しい参照カウントを実装することがさらに困難になります。

  • 重要なエラーチェックがありません。PyString_FromStringメモリが不足していると失敗する可能性があります。PyList_GetItemその間にリストが縮小すると失敗する可能性があります。成功したPyObject_GetAttr後でも、状況によっては失敗する可能性があります。PyObject_HasAttr

これは、コードの書き直された(ただしテストされていない)バージョンであり、次の変更が含まれています。

  • ユーティリティ関数は、クラスを定義するモジュールからクラスをGetExpressionTreeClass取得します。ExpressionTree(の正しいモジュール名を入力してmy_moduleください。)

  • Guardスコープを離れるときにPythonオブジェクトを解放するRAIIスタイルのガードクラスです。この小さくて単純なクラスは、参照カウントを例外安全にし、そのコンストラクターはNULLオブジェクト自体を処理します。boost::pythonこのスタイルで機能のレイヤーを定義しているので、それを確認することをお勧めします。

  • すべてのPython_exceptionスローには、Python例外情報の設定が伴います。したがって、のキャッチャーは、例外をPython_exception使用PyErr_PrintExcまたはPyErr_Fetch印刷したり、何が問題だったかを調べたりすることができます。

コード:

class Guard {
  PyObject *obj;
public:
  Guard(PyObject *obj_): obj(obj_) {
    if (!obj)
      throw Python_exception("NULL object");
  }
  ~Guard() {
    Py_DECREF(obj);
  }
};

PyObject *GetExpressionTreeClass()
{
  PyObject *module = PyImport_ImportModule("my_module");
  Guard module_guard(module);
  return PyObject_GetAttrString(module, "ExpressionTree");
}

void VisitTree(PyObject* py_tree) throw (Python_exception)
{
  PyObject *cls = GetExpressionTreeClass();
  Guard cls_guard(cls);

  PyObject* list = PyObject_GetAttrString(py_tree, "children");
  if (!list && PyErr_ExceptionMatches(PyExc_AttributeError)) {
    PyErr_Clear();  // hasattr does this exact check
    return;
  }
  Guard list_guard(list);

  Py_ssize_t size = PyList_Size(list);
  for (Py_ssize_t i = 0; i < size; i++) {
    PyObject* child = PyList_GetItem(list, i);
    Py_XINCREF(child);
    Guard child_guard(child);

    // check if child is class instance or number (terminal)
    if (PyInt_Check(child) || PyLong_Check(child) || PyString_Check(child)) 
      ; // terminal - do nothing for now
    else if (PyObject_IsInstance(child, cls))
      VisitTree(child);
    else {
      PyErr_Format(PyExc_TypeError, "unrecognized %s object", Py_TYPE(child)->tp_name);
      throw Python_exception("unrecognized object from python");
    }
  }
}
于 2012-11-12T20:30:46.907 に答える