pybind11 Python C++ Interface 完全指南:用瘦 C++ 封装从 C++ 调用 Python
2026/9/13 4:01:01 网站建设 项目流程

pybind11 Python C++ Interface 完全指南:用瘦 C++ 封装从 C++ 调用 Python

【免费下载链接】pybind11Seamless operability between C++11 and Python项目地址: https://gitcode.com/GitHub_Trending/py/pybind11

本文基于 pybind11 官方文档的 "Python C++ interface" 章节(章节入口及其三个子页 object、numpy、utilities)整理。pybind11 通过“瘦 C++ 封装”(thin C++ wrappers)暴露 Python 类型与函数,使 C++ 代码无需直接操作 Python C API 就能导入模块、调用函数、操作对象、处理 NumPy 数组,并统一管理输出与异常。读完本文,你将掌握在 C++ 侧构造 Python 对象、双向类型转换、调用 Python 函数/方法、使用py::print与 ostream 重定向、执行 Python 表达式,以及绑定 NumPy buffer/array 的完整方案。

1. 为什么需要瘦 C++ 封装

章节首页 对这一章节的定位是:

pybind11 exposes Python types and functions using thin C++ wrappers, which makes it possible to conveniently call Python code from C++ without resorting to Python's C API.

也就是说,本章节解决的是反向绑定问题——不是把 C++ 函数暴露给 Python,而是在 C++ 内部直接使用 Python 生态。其核心载体是py::object及其一组类型化子类,源码集中在 include/pybind11/pytypes.h:object类定义在该文件第 381 行,各类型封装(bool_int_strbytestuplelistdictslicenonecapsuleiterableiteratorfunctionbuffermemoryviewellipsis等)都作为object的子类分布在其后。

2. 可用的 Python 类型封装

2.1 封装类型一览

所有主要 Python 类型都有对应的瘦封装类,并且可以直接用作 C++ 函数的参数。文档列出的类型包括:handleobjectbool_int_float_strbytestuplelistdictslicenonecapsuleiterableiteratorfunctionbufferarrayarray_t

从源码结构看,这些封装在 include/pybind11/pytypes.h 中依次定义:iterator(L1522)、iterable(L1625)、str(L1632)、bytes(L1741)、none/ellipsis(L1865/L1871)、bool_(L1877)、int_(L1916)、float_(L1951)、slice(L1990)、capsule(L2033)、tuple(L2159)、dict(L2189)、list(L2249)、function(L2347)、buffer(L2365)、memoryview(L2383)。文档同时提醒:如果在 C++ API 中大量使用这些类型,务必先阅读第 6 节的 Gotchas。

2.2 从 C++ 实例化复合 Python 类型

字典可以直接在py::dict构造器中初始化,pybind11::literals命名空间提供_a字面量用于构造关键字参数:

using namespace pybind11::literals; // to bring in the `_a` literal py::dict d("spam"_a=py::none(), "eggs"_a=42);

元组通过py::make_tuple实例化,每个元素会被转换为其对应的受支持 Python 类型:

py::tuple tup = py::make_tuple(42, py::none(), "spam");

简单命名空间(Python 的types.SimpleNamespace)也可以直接构造,适合作为类实例的轻量替身;其属性可以用py::delattrpy::getattrpy::setattr修改:

using namespace pybind11::literals; // to bring in the `_a` literal py::object SimpleNamespace = py::module_::import("types").attr("SimpleNamespace"); py::object ns = SimpleNamespace("spam"_a=py::none(), "eggs"_a=42);

2.3 双向类型转换(Casting back and forth)

混合代码中经常需要把任意 C++ 类型转换为 Python 对象,正向使用py::cast

MyClass *cls = ...; py::object obj = py::cast(cls);

反向使用模板cast成员函数:

py::object obj = ...; MyClass *cls = obj.cast<MyClass *>();

两个方向在转换失败时都会抛出py::cast_error异常。文档特别指出:当转换目标是非拥有类型(如std::string_view)时,Python 源对象可能在 cast 成功后仍需要保持存活,这属于字符串/视图的生命周期问题(详见文档string_view_lifetime主题)。

2.4 隐式转换(Implicit casting)

使用 C++ 接口调用 Python 时,返回值类型是py::object。可以隐式转换到其子类(如py::dict),operator[]obj.attr()返回的代理对象同样支持隐式转换。这种隐式转换到子类型的能力能显著提升代码可读性,并允许把值直接传给要求特定子类型而非泛型object的 C++ 函数:

#include <pybind11/numpy.h> using namespace pybind11::literals; py::module_ os = py::module_::import("os"); py::module_ path = py::module_::import("os.path"); // like 'import os.path as path' py::module_ np = py::module_::import("numpy"); // like 'import numpy as np' py::str curdir_abs = path.attr("abspath")(path.attr("curdir")); py::print(py::str("Current directory: ") + curdir_abs); py::dict environ = os.attr("environ"); py::print(environ["HOME"]); py::array_t<float> arr = np.attr("ones")(3, "dtype"_a="float32"); py::print(py::repr(arr + py::int_(1)));

注意:这些隐式转换仅对object的子类可用;对于自定义 C++ 类,仍需显式调用obj.cast()。另外,如果通过移动构造器无法完成平凡转换,隐式与显式 cast 都会尝试一次“rich”(富)转换——例如py::list env = os.attr("environ");会成功,等效于 Python 的env = list(os.environ)(即把 dict 的 key 转成 list)。

3. 访问 Python 库与调用 Python 函数

3.1 导入 Python 标准库与环境中的模块

可以导入 Python 标准库或当前环境(sys.path)中的对象,并在 C++ 中直接操作。等价于from decimal import Decimal

py::object Decimal = py::module_::import("decimal").attr("Decimal");

也可以探测第三方库:

// Try to import scipy py::object scipy = py::module_::import("scipy"); return scipy.attr("__version__");

3.2 通过 operator() 调用函数

Python 的类、函数、方法都可以通过operator()直接调用:

// Construct a Python object of class Decimal py::object pi = Decimal("3.14159"); // Use Python to make our directories py::object os = py::module_::import("os"); py::object makedirs = os.attr("makedirs"); makedirs("/tmp/path/to/somewhere");

若目标类型定义了py::class_或类型转换,Python 返回的结果还可以转回纯 C++ 版本:

py::function f = <...>; py::object result_py = f(1234, "hello", some_instance); MyClass &result = result_py.cast<MyClass>();

3.3 调用方法:绑定方法与未绑定方法

调用对象方法同样通过.attr获取。注意pi.attr("exp")是一个绑定方法(bound method),它总是作用于同一实例:

// Calculate e^π in decimal py::object exp_pi = pi.attr("exp")(); py::print(py::str(exp_pi));

也可以从类(而非实例)上取未绑定方法(unbound method),显式传入self对象:

py::object decimal_exp = Decimal.attr("exp"); // Compute the e^n for n=0..4 for (int n = 0; n < 5; n++) { py::print(decimal_exp(Decimal(n))); }

3.4 关键字参数、*args 与 **kwargs 解包

Python 侧的关键字调用:

def f(number, say, to): ... # function code f(1234, say="hello", to=some_instance) # keyword call in Python

在 C++ 中用_a字面量等价实现:

using namespace pybind11::literals; // to bring in the `_a` literal f(1234, "say"_a="hello", "to"_a=some_instance); // keyword call in C++

*args/**kwargs解包可以与普通参数混用:

// * unpacking py::tuple args = py::make_tuple(1234, "hello", some_instance); f(*args); // ** unpacking py::dict kwargs = py::dict("number"_a=1234, "say"_a="hello", "to"_a=some_instance); f(**kwargs); // mixed keywords, * and ** unpacking py::tuple args = py::make_tuple(1234); py::dict kwargs = py::dict("to"_a=some_instance); f(*args, "say"_a="hello", **kwargs);

此外还支持 PEP 448 的广义解包(在关键字之间交错多个**kwargs):

py::dict kwargs1 = py::dict("number"_a=1234); py::dict kwargs2 = py::dict("to"_a=some_instance); f(**kwargs1, "say"_a="hello", **kwargs2);

3.5 异常处理

封装类中的 Python 异常会以py::error_already_set的形式抛出;关于捕获 C++ 封装类中抛出的异常,见 异常处理文档 中的 "Handling exceptions from Python in C++" 主题。

4. NumPy 集成:buffer 协议、array_t 与向量化

4.1 Buffer 协议:让 C++ 对象零拷贝进入 NumPy

Python 支持通过 buffer view 在插件库之间交换数据的通用机制。以下是一个示例Matrix类及其绑定代码——通过py::buffer_protocol()标签加def_buffer()暴露 buffer 描述,即可把Matrix实例直接 cast 为 NumPy 数组,甚至用np.array(matrix_instance, copy=False)完全避免拷贝:

class Matrix { public: Matrix(size_t rows, size_t cols) : m_rows(rows), m_cols(cols) { m_data = new float[rows*cols]; } float *data() { return m_data; } size_t rows() const { return m_rows; } size_t cols() const { return m_cols; } private: size_t m_rows, m_cols; float *m_data; }; py::class_<Matrix>(m, "Matrix", py::buffer_protocol()) .def_buffer([](Matrix &m) -> py::buffer_info { return py::buffer_info( m.data(), /* Pointer to buffer */ sizeof(float), /* Size of one scalar */ py::format_descriptor<float>::format(), /* Python struct-style format descriptor */ 2, /* Number of dimensions */ { m.rows(), m.cols() }, /* Buffer dimensions */ { sizeof(float) * m.cols(), /* Strides (in bytes) for each index */ sizeof(float) } ); });

py::buffer_info的字段镜像 Python buffer 协议规范:

struct buffer_info { void *ptr; py::ssize_t itemsize; std::string format; py::ssize_t ndim; std::vector<py::ssize_t> shape; std::vector<py::ssize_t> strides; };

反向的例子:定义一个接受py::buffer参数的自定义构造器,从兼容的 buffer 对象(如 NumPy 矩阵)初始化Eigen::MatrixXd

/* Bind MatrixXd (or some other Eigen type) to Python */ typedef Eigen::MatrixXd Matrix; typedef Matrix::Scalar Scalar; constexpr bool rowMajor = Matrix::Flags & Eigen::RowMajorBit; py::class_<Matrix>(m, "Matrix", py::buffer_protocol()) .def(py::init([](py::buffer b) { typedef Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic> Strides; /* Request a buffer descriptor from Python */ py::buffer_info info = b.request(); /* Some basic validation checks ... */ if (!info.item_type_is_equivalent_to<Scalar>()) throw std::runtime_error("Incompatible format: expected a double array!"); if (info.ndim != 2) throw std::runtime_error("Incompatible buffer dimension!"); auto strides = Strides( info.strides[rowMajor ? 0 : 1] / (py::ssize_t)sizeof(Scalar), info.strides[rowMajor ? 1 : 0] / (py::ssize_t)sizeof(Scalar)); auto map = Eigen::Map<Matrix, 0, Strides>( static_cast<Scalar *>(info.ptr), info.shape[0], info.shape[1], strides); return Matrix(map); }));

对应的def_buffer()写法(按行主/列主序计算 strides):

.def_buffer([](Matrix &m) -> py::buffer_info { return py::buffer_info( m.data(), /* Pointer to buffer */ sizeof(Scalar), /* Size of one scalar */ py::format_descriptor<Scalar>::format(), /* Python struct-style format descriptor */ 2, /* Number of dimensions */ { m.rows(), m.cols() }, /* Buffer dimensions */ { sizeof(Scalar) * (rowMajor ? m.cols() : 1), sizeof(Scalar) * (rowMajor ? 1 : m.rows()) } /* Strides (in bytes) for each index */ ); })

文档同时建议:对于绑定 Eigen 类型,有更简单(但有一定局限)的现成方案,参见 Eigen 绑定文档;tests/test_buffers.cpp 提供了 buffer 协议的完整可运行示例。

4.2 py::array 与 py::array_t:只接受 NumPy 数组

把参数类型从py::buffer换成py::array,即可限定函数只接受 NumPy 数组而非任意 buffer 协议对象。若只想接受某数据类型的 NumPy 数组,使用py::array_t<T>模板:

void f(py::array_t<double> array);

调用时若传入其他类型(如整数列表),绑定层会尝试把输入 cast 为请求的 NumPy 数组类型。该功能需要包含 include/pybind11/numpy.h 头文件;该头文件本身不依赖 NumPy 头文件,因此构建时不需要声明对 NumPy 的依赖,NumPy>=1.7.0 只是运行时依赖。

由于 NumPy 数组的数据不保证密集排布,可以再用第二个模板参数限定布局:py::array::c_style(C/行主序)或py::array::f_style(Fortran/列主序):

void f(py::array_t<double, py::array::c_style | py::array::forcecast> array);

py::array::forcecast是第二个模板参数的默认值:它确保不符合要求的参数会被转换为满足要求的数组,而不是让调用回退到下一个函数重载。

py::array还提供一组 NumPy API 风格的方法:

  • .dtype()返回包含值的类型;
  • .strides()返回 strides 指针(可传 axis 取单个值);
  • .flags()返回标志位,.writable().owndata()可直接使用;
  • .offset_at()返回偏移量(可传索引);
  • .squeeze()返回去掉长度 1 轴的视图;
  • .view(dtype)返回不同 dtype 的视图;
  • .reshape({i, j, ...})返回不同形状的视图(.resize({...})也可用);
  • .index_at(i, j, ...)返回从数组开头到给定索引的元素个数。

4.3 结构化(record)类型:PYBIND11_NUMPY_DTYPE

要让py::array_t支持结构化 C++ 类型,需先用PYBIND11_NUMPY_DTYPE宏注册其内存布局(类型后跟字段名列表):

struct A { int x; double y; }; struct B { int z; A a; }; // ... PYBIND11_MODULE(test, m, py::mod_gil_not_used()) { // ... PYBIND11_NUMPY_DTYPE(A, x, y); PYBIND11_NUMPY_DTYPE(B, z, a); /* now both A and B can be used as template arguments to py::array_t */ }

结构体成员应只由基础算术类型、std::complex、已注册子结构体及其数组组成,C++ 数组与std::array均支持。宏内有静态断言阻止多数不受支持的结构,但确保只用“plain”结构体(可安全按原始内存操作而不破坏不变量)仍由用户负责。拼写中自带逗号的类型要用PYBIND11_TYPE包裹:PYBIND11_NUMPY_DTYPE(PYBIND11_TYPE(C<int, double>), x, y)

4.4 NumPy 标量类型:py::numpy_scalar

floatdouble默认都绑定到 Python 内置float(双精度),无法在 C++ 侧区分单/双精度入参。py::numpy_scalar<T>提供了类型严格的解法:

m.def("add", [](py::numpy_scalar<float> a, py::numpy_scalar<float> b) { return py::make_scalar(a + b); }); m.def("add", [](py::numpy_scalar<double> a, py::numpy_scalar<double> b) { return py::make_scalar(a + b); });

该类型与其包裹类型可平凡互转,目前支持的标量是 NumPy 算术类型:bool_int8int64uint8uint64float32float64complex64complex128,分别映射到对应的 C++ 类型。两点注意:

  • py::numpy_scalar<T>严格匹配 NumPy 标量:py::numpy_scalar<int64_t>接受np.int64(123),但不接受普通 Pythonint(如123);
  • 原生 C 类型到 NumPy 类型的映射是平台相关的(例如char可能是np.int8np.uint8long可能是 4 或 8 字节)。除非你明确理解差异与需求,请使用<cstdint>中的定宽类型。

4.5 py::vectorize:把逐元素函数自动扩展到任意维数组

假设要绑定double my_func(int x, float y, double z);,包含pybind11/numpy.h后只需一行:

m.def("vectorized_func", py::vectorize(my_func));
>>> x = np.array([[1, 3], [5, 7]]) >>> y = np.array([[2, 4], [6, 8]]) >>> z = 3 >>> result = vectorized_func(x, y, z)

上例会对 4 个元素各调用一次my_func。相比numpy.vectorize()之类的方案,其优势是元素循环完全在 C++ 侧执行,编译器可以优化成紧凑循环;结果以numpy.dtype.float64数组返回。标量参数z会被透明地复制 4 次;输入数组xy会自动转换为目标类型(int64int32/float32)。限制:只有按值或const &传递的算术、复数、POD 类型会被向量化,其他参数原样透传;接受右值引用的函数不能向量化。

当计算过于复杂无法用vectorize表达时,需手动创建并访问 buffer。tests/test_numpy_vectorize.cpp 给出完整示例;手动方式的一个典型写法如下:

#include <pybind11/pybind11.h> #include <pybind11/numpy.h> namespace py = pybind11; py::array_t<double> add_arrays(py::array_t<double> input1, py::array_t<double> input2) { py::buffer_info buf1 = input1.request(), buf2 = input2.request(); if (buf1.ndim != 1 || buf2.ndim != 1) throw std::runtime_error("Number of dimensions must be one"); if (buf1.size != buf2.size) throw std::runtime_error("Input shapes must match"); /* No pointer is passed, so NumPy will allocate the buffer */ auto result = py::array_t<double>(buf1.size); py::buffer_info buf3 = result.request(); double *ptr1 = static_cast<double *>(buf1.ptr); double *ptr2 = static_cast<double *>(buf2.ptr); double *ptr3 = static_cast<double *>(buf3.ptr); for (size_t idx = 0; idx < buf1.shape[0]; idx++) ptr3[idx] = ptr1[idx] + ptr2[idx]; return result; } PYBIND11_MODULE(test, m, py::mod_gil_not_used()) { m.def("add_arrays", &add_arrays, "Add two NumPy arrays"); }

4.6 unchecked 直接访问:跳过维度与边界检查

处理超大数组时,往往希望跳过每次访问的维度/边界检查。py::arraypy::array_t<T>提供unchecked<N>()mutable_unchecked<N>()代理对象,N为要求的维数:

m.def("sum_3d", [](py::array_t<double> x) { auto r = x.unchecked<3>(); // x must have ndim = 3; can be non-writeable double sum = 0; for (py::ssize_t i = 0; i < r.shape(0); i++) for (py::ssize_t j = 0; j < r.shape(1); j++) for (py::ssize_t k = 0; k < r.shape(2); k++) sum += r(i, j, k); return sum; }); m.def("increment_3d", [](py::array_t<double> x) { auto r = x.mutable_unchecked<3>(); // Will throw if ndim != 3 or flags.writeable is false for (py::ssize_t i = 0; i < r.shape(0); i++) for (py::ssize_t j = 0; j < r.shape(1); j++) for (py::ssize_t k = 0; k < r.shape(2); k++) r(i, j, k) += 1.0; }, py::arg().noconvert());

py::array对象取代理需同时指定数据类型与维数,例如auto r = myarray.mutable_unchecked<float, 2>();;若编译期未知维数,可省略维数模板参数(arr_t.unchecked()arr.unchecked<T>()),得到行为相同但可优化性略差的代理。注意:代理直接引用数组数据,构造时只读取一次 shape/strides/writable 标志;必须保证被引用数组在代理存活期间不被销毁或 reshape,通常做法是限制代理的作用域。代理支持的方法包括.ndim().data(1, 2, ...)/.mutable_data(1, 2, ...)(后者仅限mutable_unchecked()得到)、.itemsize().shape(n).size().nbytes()。tests/test_numpy_array.cpp 有更多示例。

4.7 Ellipsis 与 memoryview

Python 的...省略号切片在 C++ 侧对应py::ellipsis()

py::array a = /* A NumPy array */; py::array b = a[py::make_tuple(0, py::ellipsis(), 0)];

若只想直接暴露一块 C/C++ buffer(没有具体类对象),可以返回memoryview

const uint8_t buffer[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; m.def("get_memoryview2d", []() { return py::memoryview::for // 实际调用 from_buffer });

标准写法(2×4 的 uint8 数组):

m.def("get_memoryview2d", []() { return py::memoryview::from_buffer( buffer, // buffer pointer { 2, 4 }, // shape (rows, cols) { sizeof(uint8_t) * 4, sizeof(uint8_t) } // strides in bytes ); });

对简单的一维连续 buffer 可用memoryview::from_memory(2.6 版本新增):

m.def("get_memoryview1d", []() { return py::memoryview::from_memory( buffer, // buffer pointer sizeof(uint8_t) * 8 // buffer size ); });

这类memoryview面向不受 Python 管理的 C/C++ buffer,缓冲区的生命周期由用户负责:C++ 侧释放 buffer 后再使用该memoryview属于未定义行为。

5. 实用工具:py::print、ostream 重定向与 eval/exec

5.1 用 py::print 与 Python 输出保持同一缓冲

C++ 常用std::cout,Python 用print,两者使用不同缓冲,混用会造成输出乱序。pybind11 提供py::print,它通过 Python 的打印机制输出。py::print的每次调用都会委托给当前执行帧 builtins 中的print项(通常是builtins.print);若没有 Python 帧在执行,则使用当前解释器的 builtins。标准 built-in 下,可选关键字参数sependfileflush行为与 Python 一致:

py::print(1, 2.0, "three"); // 1 2.0 three py::print(1, 2.0, "three", "sep"_a="-"); // 1-2.0-three auto args = py::make_tuple("unpacked", true); py::print("->", *args, "end"_a="<-"); // -> unpacked True <-

标准 built-in 下,省略file或传"file"_a = py::none()时使用当前sys.stdout

从源码看(include/pybind11/pybind11.h),实现分为两层:可变参数模板py::print先用detail::collect_arguments收集位置参数与关键字参数(因此支持*args/**kwargs解包),再调用detail::print;后者按 Python 版本取 builtins(3.13+ 使用PyEval_GetFrameBuiltins,之前使用PyEval_GetBuiltins),取出其中的"print"项并PyObject_Call。值得注意的是,该实现对解释器关闭期间 builtins 字典被部分清理的情况做了防御:若取不到print会静默返回,调用失败则抛error_already_set

5.2 捕获 std::cout/std::cerr:scoped_ostream_redirect

第三方库经常直接往std::cout/std::cerr写,这与 Python 的sys.stdout/sys.stderr重定向不兼容,而把库内部打印逐一替换为py::print往往不可行。此时可用作用域守护对象把 C++ 流重定向到对应 Python 流(来自 include/pybind11/iostream.h):

#include <pybind11/iostream.h> // Add a scoped redirect for your noisy code m.def("noisy_func", []() { py::scoped_ostream_redirect stream( std::cout, // std::ostream& py::module_::import("sys").attr("stdout") // Python output ); call_noisy_func(); });

线程安全警告(原文档明确强调)pybind11/iostream.h的实现不是线程安全的。多个线程并发写被重定向的 ostream 会导致数据竞争,甚至缓冲区溢出;因此所有(可能)并发的重定向写入必须加互斥锁保护。

该机制尊重输出流的 flush,并在守护对象析构时按需 flush,因此可以实时重定向(例如 Jupyter notebook)。两个参数都可省略,默认重定向标准输出。py::scoped_estream_redirect与其完全相同,只是默认针对std::cerrsys.stderr;配合支持多参数的py::call_guard(使用默认构造)可以一行搞定:

// Alternative: Call single function using call guard m.def("noisy_func", &call_noisy_function, py::call_guard<py::scoped_ostream_redirect, py::scoped_estream_redirect>());

源码中,scoped_ostream_redirect类定义于 include/pybind11/iostream.h,构造函数默认std::cout/sys.stdoutscoped_estream_redirect派生类默认std::cerr/sys.stderr

也可以在 Python 侧用上下文管理器完成重定向,只需注册py::add_ostream_redirect(定义见 include/pybind11/iostream.h):

py::add_ostream_redirect(m, "ostream_redirect");

Python 侧名称省略时默认为ostream_redirect,它会创建如下上下文管理器(默认同时重定向两个流,可用关键字参数单独关闭):

with ostream_redirect(stdout=True, stderr=True): noisy_function()

注意:以上方法不会重定向 C 层直接写文件描述符的输出(如fprintf)。这类情况需要自行在 C 侧重定向 fd,或用 Python 的os.dup2以操作系统相关的方式处理。

5.3 从字符串与文件执行 Python:eval / exec / eval_file

include/pybind11/eval.h 提供evalexeceval_file三个函数:

// At beginning of file #include <pybind11/eval.h> // Evaluate in scope of main module py::object scope = py::module_::import("__main__").attr("__dict__"); // Evaluate an isolated expression int result = py::eval("my_variable + 10", scope).cast<int>(); // Evaluate a sequence of statements py::exec( "print('Hello')\n" "print('world!');", scope); // Evaluate the statements in an separate Python file on disk py::eval_file("script.py", scope);

C++11 raw string 字面量对这段代码非常友好;唯一要求是首条语句必须紧跟在 raw string 定界符R"(之后另起一行,保证所有行有相同的前导缩进:

py::exec(R"( x = get_answer() if x == 42: print('Hello World!') else: print('Bye!') )", scope );

evaleval_file接受一个描述“字符串/文件如何被解释”的模板参数:eval_expr(独立表达式)、eval_single_statement(单条语句,返回值恒为none)、eval_statements(语句序列,返回值恒为none)。eval默认eval_expreval_file默认eval_statementsexec就是eval<eval_statements>的快捷方式。

6. 常见陷阱(Gotchas)

文档专门列出两个高频错误:

默认构造的 wrapper 不是合法 Python 对象。默认构造的 wrapper 类型既不是有效 Python 对象,也不等于py::none()——它等价于一个空PyObject*。判空应使用static_cast<bool>(my_wrapper)

不要给py::str/py::dict等赋py::none()默认值。在 C++ 签名(纯 C++ 或绑定签名)中使用这些类型并把默认值设为py::none()时:最好情况下快速失败(None无法转换到该类型,如py::dict);更糟的是静默成功但污染类型——例如py::str(py::none())在 Python 侧得到的是字符串"None"

7. 测试代码中的完整验证

文档在多处用seealso指向仓库内的可运行测试,可作为上述每个特性的完整参照实现:

主题测试文件
原生 Python 类型的传递与使用tests/test_pytypes.cpp
从 C++ 调用 Python 函数(含关键字参数与解包)tests/test_callbacks.cpp
buffer 协议tests/test_buffers.cpp
py::vectorizetests/test_numpy_vectorize.cpp
NumPy array 与 unchecked 访问tests/test_numpy_array.cpp
eval/exec/eval_filetests/test_eval.cpp、tests/test_eval.py
iostream 重定向tests/test_iostream.cpp

8. 小结

pybind11 的 "Python C++ interface" 把 Python 互操作分成三层能力:其一是类型层——py::object与一组类型化封装(dicttuplefunction等),覆盖构造、双向cast、隐式转换与error_already_set异常路径;其二是数值层——buffer 协议、py::array_t<T>PYBIND11_NUMPY_DTYPEpy::numpy_scalarpy::vectorizeunchecked<N>()直访,支撑零拷贝与高性能数组计算;其三是运行层——py::printscoped_ostream_redirect/add_ostream_redirecteval/exec/eval_file,解决输出缓冲一致性与从 C++ 执行 Python 代码的问题。使用时需牢记两个边界:iostream 重定向实现非线程安全,C 层 fd 输出不在重定向范围内;默认构造的 wrapper 不代表None,不要拿py::none()当类型默认值。相关入口文档与源码可分别见 docs/advanced/pycpp/object.rst、docs/advanced/pycpp/numpy.rst、docs/advanced/pycpp/utilities.rst、include/pybind11/pytypes.h、include/pybind11/numpy.h 与 include/pybind11/iostream.h。

【免费下载链接】pybind11Seamless operability between C++11 and Python项目地址: https://gitcode.com/GitHub_Trending/py/pybind11

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询