σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academy学习路线
Ctrl K
AO
+1.2k 位学习者
登录加入 ↗
学习路线
AO
+1.2k 位学习者
登录
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python 学习路线›模块 10 · 高性能 Python 工程›主题 10.8·~1小时高级

用 pybind11 调用 C++

路线进度模块 10/11 · 主题 8/13
10.8 用 pybind11 调用 C++
—实验得分— / 100总 XP—连续天数—

当 Python 需要 C++ 时

交易领域的许多严肃代码都是 C++:撮合引擎、行情处理程序、打磨了十五年的定价库。你很少想重写它们,也很少想为了研究而放弃 Python。pybind11 就是桥梁:这是一个只有头文件的 C++ 库,你写几行话来说“把这个 C++ 函数以这个名字暴露给 Python”,把它们编译成共享库,然后在 Python 里像任何模块一样 import。参数和结果会替你转换:Python 的浮点数列表会变成 std::vector<double>,反之亦然。

# A C++ function and the pybind11 file that exposes it to Python (shown here as text)
cpp = """\
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <vector>

namespace py = pybind11;

double mean(const std::vector<double>& x) {
    double total = 0.0;
    for (double v : x) total += v;
    return x.empty() ? 0.0 : total / x.size();
}

PYBIND11_MODULE(fastmath, m) {
    m.def("mean", &mean, "Mean of a list of floats");
}
"""
lines = cpp.splitlines()
print(lines[-2].strip())                             # m.def("mean", &mean, "Mean of a list of floats");
print(len(lines))                                    # 15

跨越桥梁的类型

每个 Python 类型都有对应的 C++ 类型:int 是 int64_t,float 是 double,str 是 std::string,浮点数列表是 std::vector<double>;其他任何东西都可以作为通用的 py::object 持有。包含 <pybind11/stl.h> 会让标准容器的转换自动完成。大的值用常量引用(const std::vector<double>& x)传递,这样 C++ 函数不会拷贝它们;数字这类小值则按值传递。把 Python 列表转换成向量仍然每次调用会拷贝一次,所以对大数组请使用 py::array_t<double>,它直接查看 NumPy 数组的内存,无需拷贝。

# Choosing the C++ type of each argument; big values are passed by const reference
CPP_TYPES = {"int": "int64_t", "float": "double", "str": "std::string", "list[float]": "std::vector<double>"}

def cpp_type(py_type):
    return CPP_TYPES.get(py_type, "py::object")

def cpp_signature(name, params, returns):
    result = "void" if returns is None else cpp_type(returns)
    arguments = []
    for param, py_type in params:
        ctype = cpp_type(py_type)
        arguments.append(f"const {ctype}& {param}" if ctype.startswith("std::") else f"{ctype} {param}")
    return f"{result} {name}({', '.join(arguments)})"

print(cpp_signature("mean", [("x", "list[float]"), ("n", "int")], "float"))      # double mean(const std::vector<double>& x, int64_t n)
print(cpp_signature("label", [("symbol", "str")], "str"))                       # std::string label(const std::string& symbol)

构建、GIL 与所有权

用 CMake(pybind11_add_module)构建,或在 setup.py 中使用 pybind11.setup_helpers,得到可以 import 的 .so 或 .pyd 文件。有两点需要知道。第一,C++ 代码运行期间,除非用 py::gil_scoped_release 释放,否则 Python 的全局解释器锁(GIL)一直被持有;不接触 Python 对象的长时间 C++ 计算应当释放它,让其他 Python 线程得以运行。第二是所有权:如果 C++ 返回指向自身内存的引用或指针,Python 的使用就不能比它活得更久(由 py::return_value_policy 控制),而没有 Python 回溯信息的崩溃通常意味着悬空指针。像前面的实验一样,要从 Python 里把绑定后的函数与纯 Python 版本对比测试。

# Binding lines and the module file, generated from a table of functions
BACKSLASH = chr(92)

def binding_line(py_name, cpp_name, doc):
    escaped = doc.replace(BACKSLASH, BACKSLASH + BACKSLASH).replace('"', BACKSLASH + '"')
    return f'm.def("{py_name}", &{cpp_name}, "{escaped}");'

def module_source(module, functions):
    lines = ["#include <pybind11/pybind11.h>", "#include <pybind11/stl.h>", "", "namespace py = pybind11;", ""]
    lines.append(f"PYBIND11_MODULE({module}, m) {{")
    for py_name, cpp_name, doc in functions:
        lines.append("    " + binding_line(py_name, cpp_name, doc))
    lines.append("}")
    return "\n".join(lines) + "\n"

source = module_source("pricing", [("mean", "mean_impl", "Mean of x"), ("price", "bs_price", 'Black-Scholes "call" price')])
print(source.splitlines()[-3])                       #     m.def("mean", &mean_impl, "Mean of x");
print(source.splitlines()[-2])                       #     m.def("price", &bs_price, "Black-Scholes \"call\" price");
# From Python the built module is used like any other; this stand-in has the same interface
import types

def mean(x):
    """Mean of a list of floats"""
    return sum(x) / len(x) if x else 0.0

fastmath = types.SimpleNamespace(mean=mean)          # stands in for `import fastmath` after the build
print(fastmath.mean([100.0, 102.0, 101.0]))          # 101.0
print(fastmath.mean.__doc__)                         # Mean of a list of floats   the doc string given to m.def
print(fastmath.mean([]))                             # 0.0   what the C++ code returns for an empty vector
崩溃不是 Python 异常C++ 代码中的 bug 可能带着段错误且不留回溯信息,让整个 Python 进程崩掉,而在交易系统里这意味着进程没了。要在边界处校验收到的大小和数值,并抛出 C++ 异常(pybind11 会把它转成 Python 异常),开发期间用警告和 sanitizer 编译,并让 C++ 暴露面保持很小:少数几个经过充分测试、承担繁重工作的函数,由 Python 负责编排。
  • pybind11 用 PYBIND11_MODULE 和 m.def(...) 行把 C++ 函数暴露给 Python;结果是可导入的 .so/.pyd 模块。
  • 类型对应:int→int64_t,float→double,str→std::string,列表→std::vector;大值用常量引用,NumPy 数组用 py::array_t。
  • 长时间纯 C++ 工作要释放 GIL,留意返回内存归谁所有,在边界处校验输入,并与纯 Python 版本对照测试。

下一节:线程与 GIL,Python 线程能并行做什么、不能做什么。

实验:评分内容

  • 用 cpp_type 把 Python 类型映射到 C++25 分
  • 用 cpp_signature 声明函数25 分
  • 用 binding_line 暴露函数25 分
  • 用 module_source 组装模块25 分
资源管理器 · lab-124-1
▾ lab-124-1
…
bash · 浏览器内
learner@quantcraft:~/lab-124-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

lab-124-1 中的所有内容将恢复为初始文件,终端也会被清空。你当前的解答将丢失,且无法撤销。

将被重置
  • main.py → 初始代码
  • 终端历史与测试输出
  • 未提交的更改
将被保留
  • 实验最高得分
  • 你的 XP 与连续天数
  • 讲解进度与笔记

登录后即可运行你的代码。

所有讲解均可免费阅读。创建账户或登录后,即可运行代码、运行测试并提交此实验。

  • 浏览器内 IDE 与 bash 终端,无需安装
  • 即时评分、XP 以及你在学习路线中的位置
创建账户登录
或
使用 GitHub 继续