这个问题是以下两个问题的延伸:
假设我有以下c ++类使用SWIG暴露给Python:
struct组件 { 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:组件*组件(const std :: string& class_name) { return m_components [class_name]; } 组件* create_component(const std :: string& class_name) { //创建一个新的组件,可能使用, //将其添加到m_components并返回组件。 //此方法还可以实例化Component的子类,它们是在c ++中定义的 //。 } private: std :: unordered_map< std :: string,Component *> m_components; }现在,在Python中,我定义了一个继承自 Component :
class PythonDerivedComponent(Component): def __init __ :组件.__ init __(self) def update(self,dt): print(DerivedComponent :: update(+ str(dt)+)) def g() print(DerivedComponent :: g())到达我可以向实体添加组件的阶段。此外,使用Flexo在(2)中描述的方法,我可以从 create_component()中检索组件在c ++中定义的派生组件类型:
e = Entity()c = e.create_component(DerivedComponent)#有类型< class'module.DerivedComponent'> ; c.f()#按预期打印DerivedComponent :: f()。现在,我的问题是是否可以从 create_component()当组件在Python中定义时?当然,我只需要能够在Python中做到这一点。
e = Entity $ b e.create_component(PythonDerivedComponent) #其他地方e = get_entity(Player)c = eponent类型< class'module.Component'> c.g()#无法调用此函数。解决方案
为了做一个完整的工作演示,稍微展开您的头文件,我最终看起来像:
#ifndef TEST_HH #define TEST_HH #include< map> #include< functional> #include< iostream> #include< string> struct组件 { 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:组件*组件(const std :: string& class_name) { return m_components [class_name ]; } 组件* create_component(const std :: string& class_name) { //创建一个新的组件,可能使用, //将其添加到m_components并返回组件。 //此方法还可以实例化Component的子类,它们是在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; //或者如果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类工作,我们不得不使用导演(否则它是永久抽象)和上一个问题的'out'类型已经被修改为应用在更多的地方,以及添加额外的功能。 (注意这里假设 class_name 的字符串将会是 arg2 返回此名称,这将删除对此假设的需要)
有两个主要的技巧,我们需要使这项工作:
添加中间 PythonComponent 类可能会带来额外的好处,您不会最终为纯C ++派生类型支付SWIG董事的成本。 (如果你想使用%pythoncode 和%rename 来玩Python游戏,只是使用组件而不是 PythonComponent ,但我没有这样做)
我的SWIG界面文件最终看起来像:
%module (Directors = 1)test %{ #includetest.hh #include< cassert> struct PythonComponent:Component { PyObject * derrived; }; %} %特性(director)PythonComponent; %include< std_string.i> //注意:现在应用于任何返回的组件* %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); } } %includetest.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 ++类型的注册,只需极少的额外工作,我写了一个关于以前的答案。
至于Python封装, PythonComponent 类型实际上并不会改变 Component 。保留的引用的细节保留为实现细节。
有了这些机制,所有我们需要做的是使这个工作是实现新的' out类型,添加了一个 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 :
<从测试import * pre = e.create_component(DerivedComponent)#有类型< class'module.DerivedComponent'> c.f()#按预期打印DerivedComponent :: f()。 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 = eponent(PythonDerivedComponent) print(type(c)) cg()#现在可以工作。当我们运行它,我们看到:
DerivedComponent :: f()< class'__main __。PythonDerivedComponent'> PythonDerivedComponent :: g(
This question is really an extension of the following two questions:
Suppose that I have the following c++ classes (simplified), which I am exposing to Python using SWIG:
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; }Now, in Python, I define a class that inherits from Component:
class PythonDerivedComponent(Component): def __init__(self): Component.__init__(self) def update(self, dt): print("DerivedComponent::update(" + str(dt) + ")") def g() print("DerivedComponent::g()")I have got to the stage where I can add components to entities. Also, using the method described by Flexo in (2), I can retrieve the derived component type from create_component() for when the component is defined in c++:
e = Entity() c = e.create_component("DerivedComponent") # Has type <class 'module.DerivedComponent'> c.f() # Prints "DerivedComponent::f()" as expected.Now, my question is whether it is possible to get the derived component type from create_component() for when the component is defined in Python? Of course, I only need to be able to do this from within Python.
e = Entity("Player") e.create_component("PythonDerivedComponent") # Somewhere else... e = get_entity("Player") c = eponent("PythonDerivedComponent") # Has type <class 'module.Component'> c.g() # Can't call this function.解决方案
In order to make a complete working demo I had to expand on your header file slightly, mine ended up looking like:
#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); } #endifMainly that fixed a few syntax errors and added a registry of types, with std::function objects that know how to create instances.
We're building on the typemaps from the two questions you referenced, so I won't talk about that part much in this answer, except to say that in order to make your example Python class work we had to use directors (otherwise it's permanently abstract) and the 'out' typemap from the previous question has been modified to be applied in more places as well as adding the extra functionality. (Note that this assumes the string with class_name will be arg2 always. You could add a function to the base class that returns this name which would remove the need for this assumption)
There are two main tricks we need to make this work:
There's a possible fringe benefit to adding the intermediate PythonComponent class, which is that you won't end up paying the cost of the SWIG directors for pure C++ derived types. (If you wanted to you could play games using %pythoncode and %rename to pretend to Python developers that they really are just using Component and not PythonComponent, but I've not done that here)
My SWIG interface file thus ended up looking like:
%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; }); } }We used %extend to implement the overload of register_component. That overload would also allow us to control the registration of native C++ types from within Python with only minimal extra effort, I wrote an answer about that previously.
As far as the Python wrapper is concerned the PythonComponent type doesn't actually change Component at all. The detail of the retained reference is kept as an implementation detail.
With these mechanics in place all we need to do in order to make this work is to implement the new 'out' typemap that adds a dynamic_cast to figure out if the C++ type in the table is really a PythonComponent and if it is use the retained PyObject instead of doing a lookup for the SWIG types.
We compile with:
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.soAnd I adapted your testcase to correct a few issues and call 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 = eponent("PythonDerivedComponent") print(type(c)) c.g() # Now works.When we run it we see:
DerivedComponent::f() <class '__main__.PythonDerivedComponent'> PythonDerivedComponent::g(
更多推荐
从c ++检索Python类型
发布评论