从 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&lt;Component*,PyObject*&gt; 的全局映射,或者通过在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 类型的主要内容,如果未能解决你的问题,请参考以下文章

从c ++中的python程序中检索stderr

如何使用从 C++ 发送的地址在 python 中获取值?

如何使用 Python 从预定义的 C++ 函数中获取变量的值?

Python C++ API:如何检索 NameError 的 lineno 属性

将图像从 python 包装器传递给 c++ 函数

从 C++ 调用 python 函数时如何将 C++ 类作为参数传递?