从 C++ 中检索 Python 类型
Posted
技术标签:
【中文标题】从 C++ 中检索 Python 类型【英文标题】:Retrieving a Python type back from c++ 【发布时间】:2014-12-13 00:41:32 【问题描述】:这个问题实际上是以下两个问题的延伸:
-
How can I implement a C++ class in Python, to be called by C++?
Swig downcasting from Base* to Derived*
假设我有以下 c++ 类(简化),我使用 SWIG 向 Python 公开:
struct Component
virtual void update(double dt);
struct DerivedComponent : public Component
void update(double dt) std::cout << "DerivedComponent::update()" << std::endl;
void f() std::cout << "DerivedComponent::f()" << std::endl;
class Entity
public:
Component* component(const std::string& class_name)
return m_components[class_name];
Component* create_component(const std::string& class_name)
// Creates a new Component, possibly using the method described in (1),
// adds it to m_components and returns the component.
// This method can also instantiate subclasses of Component that were
// defined in c++.
private:
std::unordered_map<std::string, Component*> m_components;
现在,在 Python 中,我定义了一个继承自 Component
的类:
class PythonDerivedComponent(Component):
def __init__(self):
Component.__init__(self)
def update(self, dt):
print("DerivedComponent::update(" + str(dt) + ")")
def g()
print("DerivedComponent::g()")
我已经到了可以向实体添加组件的阶段。另外,使用 (2) 中 Flexo 描述的方法,我可以从 create_component()
中检索派生的组件类型,用于在 c++ 中定义组件时:
e = Entity()
c = e.create_component("DerivedComponent") # Has type <class 'module.DerivedComponent'>
c.f() # Prints "DerivedComponent::f()" as expected.
现在,我的问题是,当组件在 Python 中定义时,是否可以从 create_component()
获取派生组件类型?当然,我只需要能够在 Python 中执行此操作即可。
e = Entity("Player")
e.create_component("PythonDerivedComponent")
# Somewhere else...
e = get_entity("Player")
c = e.component("PythonDerivedComponent") # Has type <class 'module.Component'>
c.g() # Can't call this function.
【问题讨论】:
绝对可以解决,我知道你要去哪里,你能否详细说明“也可以实例化组件的子类”是如何工作的 - 它只是(例如)std::function
知道的对象的映射如何创建每个注册类型的实例?
你完全正确。你可以在这个问题的答案中看到完整的细节:gamedev.stackexchange.com/questions/17746/…
【参考方案1】:
为了制作一个完整的工作演示,我不得不稍微扩展你的头文件,我的最终看起来像:
#ifndef TEST_HH
#define TEST_HH
#include <map>
#include <functional>
#include <iostream>
#include <string>
struct Component
virtual void update(double dt) = 0;
virtual ~Component()
;
struct DerivedComponent : public Component
void update(double) std::cout << "DerivedComponent::update()" << std::endl;
void f() std::cout << "DerivedComponent::f()" << std::endl;
static DerivedComponent *create()
return new DerivedComponent;
;
class Entity
public:
Component* component(const std::string& class_name)
return m_components[class_name];
Component* create_component(const std::string& class_name)
// Creates a new Component, possibly using the method described in (1),
// adds it to m_components and returns the component.
// This method can also instantiate subclasses of Component that were
// defined in c++.
Component *result = nullptr;
if (m_components[class_name])
result = m_components[class_name];
else if (m_registry[class_name])
result = m_registry[class_name]();
m_components[class_name] = result;
return result; // Or raise an exception if null?
void register_component(const std::string& class_name, std::function<Component*()> creator)
m_registry[class_name] = creator;
private:
std::map<std::string, Component*> m_components;
std::map<std::string, std::function<Component*()> > m_registry;
;
inline void register_builtins(Entity& e)
e.register_component("DerivedComponent", DerivedComponent::create);
#endif
主要是修复了一些语法错误并添加了一个类型注册表,其中包含知道如何创建实例的std::function
对象。
我们正在根据您引用的两个问题构建类型图,因此我不会在此答案中过多讨论该部分,只是要说,为了使您的示例 Python 类正常工作,我们必须使用导向器 (否则它是永久抽象的)并且上一个问题中的“输出”类型图已被修改以应用于更多地方以及添加额外的功能。 (请注意,这假设带有class_name
的字符串始终为arg2
。您可以向返回此名称的基类添加一个函数,这样就不需要此假设了)
我们需要两个主要技巧来完成这项工作:
-
我们需要实现
Component::register_component
的“Python 感知”版本。在本例中,我使用 C++11 lambda 函数实现了它,该函数保留了对将要生产的产品类型的引用。 (请注意,我的示例会泄漏这些类型,因为它从不减少引用计数器。如果这对您的使用有问题,您应该使用智能指针)。
我们需要保留在构造 Python 派生类型时创建的真实 PyObject
。有几种方法可以做到这一点,例如使用std::map<Component*,PyObject*>
的全局映射,或者通过在test.hh 中的Entity
类中实际添加它。我不喜欢全局变量,并假设您不想最终将 Python 接口的问题与 C++ 实现混为一谈。因此,我选择添加另一个继承自 Component 的中间抽象类,并且仅用于记住实现它的 PyObject
到底是什么。
添加中间 PythonComponent
类可能有一个附带好处,即您最终不会为纯 C++ 派生类型支付 SWIG 导向器的成本。 (如果你愿意,你可以使用%pythoncode
和%rename
来玩游戏,向Python 开发人员假装他们真的只是在使用Component
而不是PythonComponent
,但我在这里没有这样做)
因此,我的 SWIG 接口文件最终看起来像:
%module(directors=1) test
%
#include "test.hh"
#include <cassert>
struct PythonComponent : Component
PyObject *derrived;
;
%
%feature("director") PythonComponent;
%include <std_string.i>
// Note: this now gets applied to anything returning Component *
%typemap(out) Component *
const PythonComponent * const pycomp = dynamic_cast<PythonComponent*>($1);
if (pycomp)
$result = pycomp->derrived;
Py_INCREF($result);
else
const std::string lookup_typename = *arg2 + " *";
swig_type_info * const outtype = SWIG_TypeQuery(lookup_typename.c_str());
$result = SWIG_NewPointerObj(SWIG_as_voidptr($1), outtype, $owner);
%include "test.hh"
struct PythonComponent : Component
;
%extend Entity
void register_component(const std::string& class_name, PyObject *python_type)
assert(PyCallable_Check(python_type));
Py_INCREF(python_type);
$self->register_component(class_name, [python_type]()
PyObject *pyinstance = PyObject_CallObject(python_type, NULL);
void *result;
const auto res = SWIG_ConvertPtr(pyinstance, &result,SWIGTYPE_p_PythonComponent, 0);
if (!SWIG_IsOK(res))
assert(false); // TODO: raise exception
const auto out = reinterpret_cast<PythonComponent *>(result);
out->derrived = pyinstance;
return out;
);
我们使用%extend
来实现register_component
的重载。这种重载还允许我们在 Python 中控制本地 C++ 类型的注册,只需极少的额外工作,I wrote an answer about that previously。
就 Python 包装器而言,PythonComponent
类型实际上根本不会更改 Component
。保留参考的细节作为实现细节保留。
有了这些机制,为了完成这项工作,我们需要做的就是实现新的“输出”类型映射,它添加了一个dynamic_cast
,以确定表中的 C++ 类型是否真的是一个PythonComponent
如果是使用保留的 PyObject
而不是查找 SWIG 类型。
我们编译:
swig -py3 -c++ -python -Wall test.i
g++ -std=c++11 -Wall -Wextra test_wrap.cxx -I/usr/include/python3.4/ -lpython3.4m -shared -o _test.so
我调整了您的测试用例以纠正一些问题并致电register_component
:
from test import *
e = Entity()
register_builtins(e)
c = e.create_component("DerivedComponent") # Has type <class 'module.DerivedComponent'>
c.f() # Prints "DerivedComponent::f()" as expected.
class PythonDerivedComponent(PythonComponent):
def update(self, dt):
print("PythonDerivedComponent::update(" + str(dt) + ")")
def g(self):
print("PythonDerivedComponent::g()")
e.register_component("PythonDerivedComponent", PythonDerivedComponent)
e.create_component("PythonDerivedComponent")
c = e.component("PythonDerivedComponent")
print(type(c))
c.g() # Now works.
当我们运行它时,我们会看到:
DerivedComponent::f()
<class '__main__.PythonDerivedComponent'>
PythonDerivedComponent::g(
【讨论】:
以上是关于从 C++ 中检索 Python 类型的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Python 从预定义的 C++ 函数中获取变量的值?