You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义C++ Data结构无法自动转为Python内置对象问题排查

问题描述

我希望将自定义的C++ Data结构自动转换为Python内置对象。以下是我的C++实现:

// data in dict
template <typename... Ts>
class Data
{
public:
    using variant_type = std::variant<Ts...>;

    Data() : _value(variant_type{}) {}

    template <typename T>
    Data(const T &value) : _value(value) {}

    template <typename T>
    Data<Ts...> &operator=(const T &V)
    {
        _value = V;
        return *this;
    }

    template<typename... Us>
    bool operator==(const Data<Us...>& other) const
    {
        if (_value.index() != other.get_variant().index())
            return false;
        return _value == other.get_variant();
    }

    template <typename T>
    T &get()
    {
        return std::get<T>(_value);
    }

    template <typename T>
    const T &get() const
    {
        return std::get<T>(_value);
    }

    void set(const variant_type &V)
    {
        _value = V;
    }

    template <typename T>
    bool is() const
    {
        return std::holds_alternative<T>(_value);
    }

    variant_type _value;

    const auto index() const
    {
        return _value.index();
    }

    variant_type get_variant() const
    {
        return _value;
    }
};

我通过pybind11定义了type_caster以实现自动转换,代码如下:

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/cast.h>
#include "dict.h"
#include <iostream>

using TypedData = Data<int, std::string>;

namespace py = pybind11;

namespace pybind11
{
    namespace detail
    {
        template <>
        struct type_caster<TypedData>
        {
        public:
            PYBIND11_TYPE_CASTER(TypedData, const_name("TypedData"));

            static handle cast(const TypedData &d, return_value_policy, handle)
            {
                switch (d.index())
                {
                case 0:
                    return py::cast(d.get<int>()).inc_ref();
                case 1:
                    return py::cast(d.get<std::string>()).inc_ref();
                }
                throw std::runtime_error("Unreachable");
            }

            bool load(handle src, bool)
            {
                if (isinstance<py::int_>(src))
                {
                    value = TypedData(py::cast<int>(src));
                }
                else if (isinstance<py::str>(src))
                {
                    value = TypedData(py::cast<std::string>(src));
                }
                else
                    return false;
                return true;
            }
        };
    } // namespace detail
} // namespace pybind11

PYBIND11_MODULE(example, m)
{
    py::class_<TypedData>(m, "TypedData")
        .def(py::init<std::variant<int, std::string>>());
}

但在Python中调用时,实例化的对象并未转为预期的int或str:

In [1]: import example

In [2]: a = example.TypedData(42)

In [3]: a
Out[3]: <example.TypedData at 0x7f6c98aaa1b0>  # 期望返回int或str

请问我哪里出错了?


问题原因与解决方法

问题核心是同时注册了py::class_和type_caster,pybind11会优先使用py::class_生成的Python类,因此实例化后得到的是TypedData对象,而非自动转换后的内置类型。

要实现自动转换,需做以下修改:

1. 移除py::class_注册代码

删除模块定义中的py::class_<TypedData>(m, "TypedData").def(py::init<std::variant<int, std::string>>());,type_caster已负责处理类型转换,无需再将TypedData暴露为Python类。

2. 启用隐式转换(可选)

在type_caster的定义中,给PYBIND11_TYPE_CASTER宏添加第三个参数,指定支持隐式转换:

PYBIND11_TYPE_CASTER(TypedData, const_name("TypedData"), converter_flags::convertible);

3. 绑定C++函数实现类型转换

此时pybind11会自动处理Python与C++之间的类型转换:

  • 当C++函数接收TypedData参数时,Python传入int/string会自动转为TypedData
  • 当C++函数返回TypedData时,会自动转为对应的Python内置类型

修改后的模块代码示例:

PYBIND11_MODULE(example, m)
{
    // 示例:返回TypedData的函数,会自动转为Python内置类型
    m.def("get_int_data", []() -> TypedData {
        return TypedData(123);
    });

    // 示例:接收TypedData参数的函数,Python可直接传int/string
    m.def("process_data", [](TypedData d) {
        if (d.is<int>()) {
            return d.get<int>() * 2;
        } else {
            return d.get<std::string>() + "_processed";
        }
    });
}

在Python中调用效果:

import example

# 自动转为int
res = example.get_int_data()
print(res)  # 输出123,类型为int

# 自动将int转为TypedData处理
print(example.process_data(42))  # 输出84

# 自动将str转为TypedData处理
print(example.process_data("test"))  # 输出test_processed

备选方案:保留TypedData类并模拟内置类型

如果需要在Python中显式创建TypedData对象,同时让它表现得像内置类型,可以给py::class_添加__repr__、__int__、__str__等方法:

py::class_<TypedData>(m, "TypedData")
    .def(py::init<int>())
    .def(py::init<std::string>())
    .def("__repr__", [](const TypedData& d) {
        if (d.is<int>()) {
            return std::to_string(d.get<int>());
        } else {
            return "\"" + d.get<std::string>() + "\"";
        }
    })
    .def("__int__", [](const TypedData& d) { return d.get<int>(); })
    .def("__str__", [](const TypedData& d) {
        if (d.is<int>()) {
            return std::to_string(d.get<int>());
        } else {
            return d.get<std::string>();
        }
    });

这种方式下,对象仍为TypedData实例,但打印或转换时会表现出内置类型的行为。


内容的提问来源于stack exchange,提问作者Roy_Kid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 02:25:03