9

私は現在 Boost.Python を使用していますが、難しい問題を解決するための助けが必要です。

環境

C++ メソッド/関数が Python に公開される場合、GIL (グローバル インタープリター ロック) を解放して、他のスレッドがインタープリターを使用できるようにする必要があります。このようにして、Python コードが C++ 関数を呼び出すときに、インタープリターを他のスレッドで使用できます。今のところ、各 C++ 関数は次のようになります。

// module.cpp
int myfunction(std::string question)
{
    ReleaseGIL unlockGIL;
    return 42;
}

それをブースト python に渡すには、次のようにします。

// python_exposure.cpp
BOOST_PYTHON_MODULE(PythonModule)
{
    def("myfunction", &myfunction);
}

問題

このスキームは正常に機能しますが、正当な理由もなくmodule.cpp依存していることを意味します。Boost.Python理想的には、 のみpython_exposure.cppに依存する必要がありますBoost.Python

解決?

私の考えは、Boost.Function次のように関数呼び出しをラップすることでした。

// python_exposure.cpp
BOOST_PYTHON_MODULE(PythonModule)
{
    def("myfunction", wrap(&myfunction));
}

ここでwrapは、への呼び出し中に GIL のロックを解除する責任がありますmyfunction。このメソッドの問題は、再実装を意味するものとwrap同じ署名が必要なことです...myfunctionBoost.Function

誰かがこの問題について何か提案があれば、私は非常に感謝しています.

4

2 に答える 2

13

ファンクターをメソッドとして公開することは公式にはサポートされていません。サポートされているアプローチは、メンバー関数に委譲する非メンバー関数を公開することです。ただし、これにより大量の定型コードが生成される可能性があります。

私が知る限り、Boost.Python の実装は、インスタンスをpython::objectメソッドとして公開できるため、ファンクターを明示的に排除していません。ただし、Boost.Python では、メソッドとして公開されるオブジェクトのタイプにいくつかの要件があります。

  • ファンクターは CopyConstructible です。
  • ファンクタは呼び出し可能です。つまり、インスタンスoを呼び出すことができますo(a1, a2, a3)
  • 呼び出し署名は、実行時にメタデータとして利用できる必要があります。Boost.Python はboost::python::detail::get_signature()関数を呼び出して、このメタデータを取得します。メタデータは、適切な呼び出しをセットアップするため、および Python から C++ にディスパッチするために内部的に使用されます。

後者の要件は、複雑になるところです。すぐにはわからない何らかの理由で、Boost.Python はget_signature()修飾された ID を介して呼び出し、引数に依存するルックアップを防ぎます。したがって、すべての候補はget_signature()、呼び出し元のテンプレートの定義コンテキストの前に宣言する必要があります。たとえば、そのためのオーバーロードのみget_signature()が考慮され、それを呼び出すテンプレートの定義の前に宣言されたものclass_( 、def()、およびなど) が考慮されmake_function()ます。この動作を説明するには、Boost.Python でファンクターを有効にするときに、Boost.Pythonget_signature()を含める前にオーバーロードを提供するか、署名を表すメタシーケンスを明示的に に提供する必要がありますmake_function()


ファンクターのサポートを有効にする例と、ガードをサポートするファンクターを提供する例を見てみましょう。C++11 の機能を使用しないことにしました。そのため、可変個引数テンプレートで削減できる定型コードがいくつかあります。さらに、すべての例では、2 つの非メンバー関数と 2 つのメンバー関数を持つspamクラスを提供する同じモデルを使用します。

/// @brief Mockup class with member functions.
class spam
{
public:
  void action()
  {
    std::cout << "spam::action()"  << std::endl;
  }

  int times_two(int x)
  {
    std::cout << "spam::times_two()" << std::endl;
    return 2 * x;
  }
};

// Mockup non-member functions.
void action()
{
  std::cout << "action()"  << std::endl;
}

int times_two(int x)
{
  std::cout << "times_two()" << std::endl;
  return 2 * x;
}

有効にするboost::function

Boost.Function の優先構文を使用する場合、署名を Boost.Python の要件を満たすメタデータに分解するには、Boost.FunctionTypes使用します。boost::function以下は、Boost.Python メソッドとしてファンクターを公開できるようにする完全な例です。

#include <iostream>

#include <boost/function.hpp>
#include <boost/function_types/components.hpp>

namespace boost  {
namespace python {
namespace detail {
// get_signature overloads must be declared before including
// boost/python.hpp.  The declaration must be visible at the
// point of definition of various Boost.Python templates during
// the first phase of two phase lookup.  Boost.Python invokes the
// get_signature function via qualified-id, thus ADL is disabled.

/// @brief Get the signature of a boost::function.
template <typename Signature>
inline typename boost::function_types::components<Signature>::type
get_signature(boost::function<Signature>&, void* = 0)
{
  return typename boost::function_types::components<Signature>::type();
}

} // namespace detail
} // namespace python
} // namespace boost

#include <boost/python.hpp>

/// @brief Mockup class with member functions.
class spam
{
public:
  void action()
  {
    std::cout << "spam::action()"  << std::endl;
  }

  int times_two(int x)
  {
    std::cout << "spam::times_two()" << std::endl;
    return 2 * x;
  }
};

// Mockup non-member functions.
void action()
{
  std::cout << "action()"  << std::endl;
}

int times_two(int x)
{
  std::cout << "times_two()" << std::endl;
  return 2 * x;
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Expose class and member-function.
  python::class_<spam>("Spam")
    .def("action",  &spam::action)
    .def("times_two", boost::function<int(spam&, int)>(
        &spam::times_two))
    ;

  // Expose non-member function.
  python::def("action",  &action);
  python::def("times_two", boost::function<int()>(
      boost::bind(&times_two, 21)));
}

そしてその使用法:

>>> import example
>>> spam = example.Spam()
>>> spam.action()
spam::action()
>>> spam.times_two(5)
spam::times_two()
10
>>> example.action()
action()
>>> example.times_two()
times_two()
42

メンバー関数を呼び出すファンクターを提供する場合、提供されるシグネチャは非メンバー関数と同等である必要があります。この場合、int(spam::*)(int)となりint(spam&, int)ます。

// ...
  .def("times_two", boost::function<int(spam&, int)>(
        &spam::times_two))
  ;

また、引数は でファンクターにバインドできますboost::bind。たとえば、すでにファンクターにバインドされているため、呼び出しexample.times_two()で引数を提供する必要はありません。21

python::def("times_two", boost::function<int()>(
    boost::bind(&times_two, 21)));

ガード付きのカスタム ファンクター

上記の例を拡張すると、Boost.Python でカスタム ファンクター タイプを使用できるようになります。RAIIguarded_functionを使用し、 RAII オブジェクトの存続期間中にラップされた関数のみを呼び出す、というファンクターを作成しましょう。

/// @brief Functor that will invoke a function while holding a guard.
///        Upon returning from the function, the guard is released.
template <typename Signature,
          typename Guard>
class guarded_function
{
public:

  typedef typename boost::function_types::result_type<Signature>::type
      result_type;

  template <typename Fn>
  guarded_function(Fn fn)
    : fn_(fn)
  {}

  result_type operator()()
  {
    Guard g;
    return fn_();
  }

  // ... overloads for operator()

private:
  boost::function<Signature> fn_;
};

guarded_function、Pythonwithステートメントと同様のセマンティクスを提供します。したがって、Boost.Python API の名前の選択を維持するために、with()C++ 関数はファンクターを作成する方法を提供します。

/// @brief Create a callable object with guards.
template <typename Guard,
          typename Fn>
boost::python::object
with(Fn fn)
{
   return boost::python::make_function(
     guarded_function<Guard, Fn>(fn), ...);
}

これにより、邪魔にならない方法でガード付きで実行される関数を公開できます。

class no_gil; // Guard

// ...
  .def("times_two", with<no_gil>(&spam::times_two))
  ;

さらに、with()関数は関数シグネチャを推測する機能を提供し、メタデータ シグネチャを明示的に Boost.Python にオーバーロードするのではなく提供できるようにしますboost::python::detail::get_signature()

2 つの RAII タイプを使用した完全な例を次に示します。

  • no_gil: コンストラクタで GIL を解放し、デストラクタで GIL を再取得します。
  • echo_guard: コンストラクタとデストラクタで出力します。
#include <iostream>

#include <boost/function.hpp>
#include <boost/function_types/components.hpp>
#include <boost/function_types/function_type.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/python.hpp>
#include <boost/tuple/tuple.hpp>

namespace detail {

/// @brief Functor that will invoke a function while holding a guard.
///        Upon returning from the function, the guard is released.
template <typename Signature,
          typename Guard>
class guarded_function
{
public:

  typedef typename boost::function_types::result_type<Signature>::type
      result_type;

  template <typename Fn>
  guarded_function(Fn fn)
    : fn_(fn)
  {}

  result_type operator()()
  {
    Guard g;
    return fn_();
  }

  template <typename A1>
  result_type operator()(A1 a1)
  {
    Guard g;
    return fn_(a1);
  }

  template <typename A1, typename A2>
  result_type operator()(A1 a1, A2 a2)
  {
    Guard g;
    return fn_(a1, a2);
  }

private:
  boost::function<Signature> fn_;
};

/// @brief Provides signature type.
template <typename Signature>
struct mpl_signature
{
  typedef typename boost::function_types::components<Signature>::type type;
};

// Support boost::function.
template <typename Signature>
struct mpl_signature<boost::function<Signature> >:
  public mpl_signature<Signature>
{};

/// @brief Create a callable object with guards.
template <typename Guard,
          typename Fn,
          typename Policy>
boost::python::object with_aux(Fn fn, const Policy& policy)
{
  // Obtain the components of the Fn.  This will decompose non-member
  // and member functions into an mpl sequence.
  //   R (*)(A1)    => R, A1
  //   R (C::*)(A1) => R, C*, A1
  typedef typename mpl_signature<Fn>::type mpl_signature_type;

  // Synthesize the components into a function type.  This process
  // causes member functions to require the instance argument.
  // This is necessary because member functions will be explicitly
  // provided the 'self' argument.
  //   R, A1     => R (*)(A1)
  //   R, C*, A1 => R (*)(C*, A1)
  typedef typename boost::function_types::function_type<
      mpl_signature_type>::type signature_type;

  // Create a callable boost::python::object that delegates to the
  // guarded_function.
  return boost::python::make_function(
    guarded_function<signature_type, Guard>(fn),
    policy, mpl_signature_type());
}

} // namespace detail

/// @brief Create a callable object with guards.
template <typename Guard,
          typename Fn,
          typename Policy>
boost::python::object with(const Fn& fn, const Policy& policy)
{
  return detail::with_aux<Guard>(fn, policy);
}

/// @brief Create a callable object with guards.
template <typename Guard,
          typename Fn>
boost::python::object with(const Fn& fn)
{
  return with<Guard>(fn, boost::python::default_call_policies());
}

/// @brief Mockup class with member functions.
class spam
{
public:
  void action()
  {
    std::cout << "spam::action()"  << std::endl;
  }

  int times_two(int x)
  {
    std::cout << "spam::times_two()" << std::endl;
    return 2 * x;
  }
};

// Mockup non-member functions.
void action()
{
  std::cout << "action()"  << std::endl;
}

int times_two(int x)
{
  std::cout << "times_two()" << std::endl;
  return 2 * x;
}

/// @brief Guard that will unlock the GIL upon construction, and
///        reacquire it upon destruction.
struct no_gil
{
public:
  no_gil()  { state_ = PyEval_SaveThread(); 
              std::cout << "no_gil()" << std::endl; }
  ~no_gil() { std::cout << "~no_gil()" << std::endl;
              PyEval_RestoreThread(state_); }
private:
  PyThreadState* state_;
};

/// @brief Guard that prints to std::cout.
struct echo_guard 
{
  echo_guard()  { std::cout << "echo_guard()" << std::endl;  }
  ~echo_guard() { std::cout << "~echo_guard()" << std::endl; }
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Expose class and member-function.
  python::class_<spam>("Spam")
    .def("action", &spam::action)
    .def("times_two", with<no_gil>(&spam::times_two))
    ;

  // Expose non-member function.
  python::def("action", &action);
  python::def("times_two", with<boost::tuple<no_gil, echo_guard> >(
      &times_two));
}

そしてその使用法:

>>> import example
>>> spam = example.Spam()
>>> spam.action()
spam::action()
>>> spam.times_two(5)
no_gil()
spam::times_two()
~no_gil()
10
>>> example.action()
action()
>>> example.times_two(21)
no_gil()
echo_guard()
times_two()
~echo_guard()
~no_gil()
42

次のようなコンテナー タイプを使用して複数のガードを提供する方法に注意してboost::tupleください。

  python::def("times_two", with<boost::tuple<no_gil, echo_guard> >(
      &times_two));

Python で呼び出すとexample.times_two(21)、次の出力が生成されます。

no_gil()
echo_guard()
times_two()
~echo_guard()
~no_gil()
42
于 2013-09-06T01:14:44.200 に答える
2

誰かが興味を持っている場合は、Tanner Sansburyの最後の作業例を使用するときに、彼のコードに小さな問題がありました。何らかの理由で、最終的に生成された署名に間違った署名があることについて彼が言及した問題がまだありましたboost::function

// example for spam::times_two:
//   correct signature (manual)
int (spam::*, int)
//   wrong signature (generated in the `guarded_function` wrapper)
int (spam&, int)

過負荷時でもboost::python::detail::get_signature()。これの責任者はboost::function_types::components; ClassTranform = add_reference<_>このクラス参照を作成するデフォルトのテンプレート パラメータがあります。これを修正するために、mpl_signature構造体を次のように変更しました。

// other includes
# include <boost/type_traits/add_pointer.hpp>
# include <boost/mpl/placeholders.hpp>

template <typename Signature> struct mpl_signature
{
  typedef typename boost::function_types::components<Signature,
                boost::add_pointer<boost::mpl::placeholders::_> >::type type;
};

template <typename Signature> struct mpl_signature<boost::function<Signature> >
{
  typedef typename boost::function_types::components<Signature>::type type;
};

そして今、すべてが魔法のように機能します。

誰かがこれが本当に正しい修正であることを確認できれば、私は興味があります:)

于 2013-11-20T18:30:07.170 に答える