从c ++检索Python类型

编程入门 行业动态 更新时间:2024-10-20 09:24:31
本文介绍了从c ++检索Python类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

这个问题是以下两个问题的延伸:

  • 如何在Python中实现一个C ++类,由C ++调用?
  • Swig downcasting from Base * to Derived *
  • 假设我有以下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 返回此名称,这将删除对此假设的需要)

    有两个主要的技巧,我们需要使这项工作:

  • 我们需要实现 Component :: register_component 的Python感知版本。在这个例子中,我使用C ++ 11 lambda函数实现它,它保留了对它将要产生的产品类型的引用。 (请注意,我的示例将泄漏这些类型,因为它从不减少引用计数器。如果这是一个问题,你应该使用智能指针)。
  • 我们需要保持在构建我们的Python派生类型时创建的真实 PyObject 。有几种方法可以做到,例如使用 std :: map< Component *,PyObject *> 的全局映射,或者实际上将它添加到 Entity 在test.hh中的类。我不喜欢全局变量,并且假设你不想结束与C +++实现的Python接口的混淆。因此,我选择了添加另一个中间抽象类,继承自Component,仅用于记住实现它的 PyObject 。
  • 添加中间 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:

  • How can I implement a C++ class in Python, to be called by C++?
  • Swig downcasting from Base* to Derived*
  • 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); } #endif

    Mainly 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:

  • We need to implement a 'Python aware' version of Component::register_component. In this instance I've implemented it with a C++11 lambda function, which retains a reference to the type of the product it is going to produce. (Note that my example would leak these types, because it never decrements the reference counter. You should use a smart pointer if that's a problem for your usage).
  • We need to keep a hold of the real PyObject that was created when constructing our Python derived types. There are several ways you could do that, for example with a global map of std::map<Component*,PyObject*>, or by actually adding it within your Entity class in test.hh. I dislike globals and worked on the assumption that you don't want to end up mixing concerns of the Python interface with the C+++ implementation. As a result I opted to add another, intermediate abstract class that inherits from Component and serves only to remember what the PyObject that implements it really was.
  • 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.so

    And 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类型

    本文发布于:2023-10-27 03:15:36,感谢您对本站的认可!
    本文链接:https://www.elefans.com/category/jswz/34/1532147.html
    版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
    本文标签:类型   Python

    发布评论

    评论列表 (有 0 条评论)
    草根站长

    >www.elefans.com

    编程频道|电子爱好者 - 技术资讯及电子产品介绍!