从c ++中检索Python类型

时间:2014-12-13 00:41:33

标签: python c++ swig

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

  1. How can I implement a C++ class in Python, to be called by C++?
  2. Swig downcasting from Base* to Derived*
  3. 假设我有以下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()")
    

    我已经到了可以向实体添加组件的阶段。另外,使用Flexo在(2)中描述的方法,我可以从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.
    

1 个答案:

答案 0 :(得分:3)

为了完成一个完整的工作演示,我不得不稍微扩展你的头文件,我的结果看起来像:

#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类工作我们有使用董事(否则它是永久抽象的)和&#39; out&#39;上一个问题中的typemap已经过修改,可以在更多地方应用,也可以添加额外的功能。 (请注意,这假设带有class_name的字符串始终为arg2。您可以向返回此名称的基类添加一个函数,这将消除对此假设的需要)

我们需要两个主要技巧来完成这项工作:

  1. 我们需要实现一个“Python意识”&#39;版本Component::register_component。在这种情况下,我使用C ++ 11 lambda函数实现了它,它保留了对它将要生成的产品类型的引用。 (请注意,我的示例会泄漏这些类型,因为它永远不会减少引用计数器。如果这对您的使用有问题,您应该使用智能指针。)
  2. 我们需要保留构造Python派生类型时创建的真实PyObject。有几种方法可以做到这一点,例如使用std::map<Component*,PyObject*>的全局地图,或者在test.hh中的Entity类中实际添加它。我不喜欢全局变量,并且假设你不希望最终混淆Python接口与C +++实现的关注点。因此,我选择添加另一个继承自Component的中间抽象类,仅用于记住实现它的PyObject
  3. 添加中间PythonComponent类可能会带来额外的好处,这就是您不会为纯C ++派生类型支付SWIG导向器的成本。 (如果您希望自己可以使用%pythoncode%rename来玩游戏,假装他们真的只使用Component而不是PythonComponent,但是我可以使用%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; }); } } 。我没有这样做)

    我的SWIG界面文件最终看起来像:

    %extend

    我们使用register_component来实现PythonComponent的重载。这种重载还允许我们在Python中控制本机C ++类型的注册,只需要很少的额外工作I wrote an answer about that previously

    就Python包装器而言,Component类型实际上并没有真正改变dynamic_cast。保留引用的详细信息将作为实现细节保留。

    有了这些机制,我们需要做的就是让这项工作是实施新的&#39; out&#39;添加PythonComponent的typemap,用于确定表中的C ++类型是否真的是PyObject,是否使用保留的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 而不是查找SWIG类型。< / p>

    我们编译:

    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(
    

    当我们运行它时,我们看到:

    {{1}}