【问题标题】:Python C API - How to construct object from PyObjectPython C API - 如何从 PyObject 构造对象
【发布时间】:2016-01-21 02:59:46
【问题描述】:

我正在寻找是否有一种不错的“原生”方式来构造一个给定 PyObject* 已知类型的对象。

这是我的代码:

C++

void add_component(boost::python::object& type)
{
    auto constructed_type = type(); // doesn't construct anything!
}

Python

o = GameObject()
o.add_component(CameraComponent)

我的代码完美地执行了整个函数,但从未为CameraComponent 触发构造函数。

所以我的问题是,给定一个已知为类型的PyObject*,我如何构造该类型的实例?

非常感谢。

【问题讨论】:

  • 如果通过构造函数你的意思是 CameraComponent 是一个 Python 类,并且应该调用它的 __init__ 方法,那么这对我来说很好,使用 boost 1.54。如果你的意思不同,请澄清。 (如果GameObject包装了一个C++类,did you read the "Constructors" part of the documentation?)
  • 你能发一个minimal reproducible example吗?我无法重现该问题(请参阅demo)。
  • 原来是编译器优化调用的问题。

标签: python c++ boost boost-python


【解决方案1】:

如果boost::python::object 引用了一个类型,那么调用它将构造一个具有引用类型的对象:

boost::python::object type = /* Py_TYPE */;
boost::python::object object = type(); // isinstance(object, type) == True

由于 Python 中的几乎所有内容都是对象,因此接受来自 Python 的参数为 boost::python::object 将允许任何类型的对象,即使是那些不是类型的对象。只要对象是可调用的(__call___),那么代码就会成功。


另一方面,如果要保证提供了一个类型,那么一个解决方案是创建一个表示 Python 类型的 C++ 类型,接受它作为参数,并使用自定义转换器来构造 C++ 类型仅当提供 Python 类型时。

以下type_object C++ 类型表示一个 Python 对象,它是一个Py_TYPE

/// @brief boost::python::object that refers to a type.
struct type_object: 
  public boost::python::object
{
  /// @brief If the object is a type, then refer to it.  Otherwise,
  ///        refer to the instance's type.
  explicit
  type_object(boost::python::object object):
    boost::python::object(object)
  {
    if (!PyType_Check(object.ptr()))
    {
      throw std::invalid_argument("type_object requires a Python type");
    }
  }
};

...

// Only accepts a Python type.
void add_component(type_object type) { ... }

如果提供的PyObject*Py_TYPE,则以下自定义转换器将仅构造type_object 实例:

/// @brief Enable automatic conversions to type_object.
struct enable_type_object
{
  enable_type_object()
  {
    boost::python::converter::registry::push_back(
      &convertible,
      &construct,
      boost::python::type_id<type_object>());
  }

  static void* convertible(PyObject* object)
  {
    return PyType_Check(object) ? object : NULL;
  }

  static void construct(
    PyObject* object,
    boost::python::converter::rvalue_from_python_stage1_data* data)
  {
    // Obtain a handle to the memory block that the converter has allocated
    // for the C++ type.
    namespace python = boost::python;
    typedef python::converter::rvalue_from_python_storage<type_object>
                                                                 storage_type;
    void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;

    // Construct the type object within the storage.  Object is a borrowed 
    // reference, so create a handle indicting it is borrowed for proper
    // reference counting.
    python::handle<> handle(python::borrowed(object));
    new (storage) type_object(python::object(handle));

    // Set convertible to indicate success. 
    data->convertible = storage;
  }
};

...

BOOST_PYTHON_MODULE(...)
{
  enable_type_object(); // register type_object converter.
}

这是一个完整的示例demonstrating,它公开了一个需要 Python 类型的函数,然后构造了该类型的一个实例:

#include <iostream>
#include <stdexcept> // std::invalid_argument
#include <boost/python.hpp>

/// @brief boost::python::object that refers to a type.
struct type_object: 
  public boost::python::object
{
  /// @brief If the object is a type, then refer to it.  Otherwise,
  ///        refer to the instance's type.
  explicit
  type_object(boost::python::object object):
    boost::python::object(object)
  {
    if (!PyType_Check(object.ptr()))
    {
      throw std::invalid_argument("type_object requires a Python type");
    }
  }
};

/// @brief Enable automatic conversions to type_object.
struct enable_type_object
{
  enable_type_object()
  {
    boost::python::converter::registry::push_back(
      &convertible,
      &construct,
      boost::python::type_id<type_object>());
  }

  static void* convertible(PyObject* object)
  {
    return PyType_Check(object) ? object : NULL;
  }

  static void construct(
    PyObject* object,
    boost::python::converter::rvalue_from_python_stage1_data* data)
  {
    // Obtain a handle to the memory block that the converter has allocated
    // for the C++ type.
    namespace python = boost::python;
    typedef python::converter::rvalue_from_python_storage<type_object>
                                                                 storage_type;
    void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;

    // Construct the type object within the storage.  Object is a borrowed 
    // reference, so create a handle indicting it is borrowed for proper
    // reference counting.
    python::handle<> handle(python::borrowed(object));
    new (storage) type_object(python::object(handle));

    // Set convertible to indicate success. 
    data->convertible = storage;
  }
};

// Mock API.
struct GameObject {};
struct CameraComponent
{
  CameraComponent()
  {
    std::cout << "CameraComponent()" << std::endl;
  }
};

boost::python::object add_component(GameObject& /* self */, type_object type)
{
  auto constructed_type = type();
  return constructed_type;
}

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

  // Enable receiving type_object as arguments.
  enable_type_object();

  python::class_<GameObject>("GameObject")
    .def("add_component", &add_component);

  python::class_<CameraComponent>("CameraComponent");
}

互动使用:

>>> import example
>>> game = example.GameObject()
>>> component = game.add_component(example.CameraComponent)
CameraComponent()
>>> assert(isinstance(component, example.CameraComponent))
>>> try:
...     game.add_component(component) # throws Boost.Python.ArgumentError
...     assert(False)
... except TypeError:
...     assert(True)
...

【讨论】:

    【解决方案2】:

    这实际上一直在工作,但是编译器以某种方式优化了构造函数逻辑,所以我的断点从未被命中!

    【讨论】:

    • 编译器 - “不知何故” - 优化。编译器的存在是有原因的 :)
    猜你喜欢
    • 2020-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-14
    相关资源
    最近更新 更多