σμ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.7·~1小时高级

Cython

路线进度模块 10/11 · 主题 7/13
10.7 Cython
—实验得分— / 100总 XP—连续天数—

带 C 类型的 Python,经过编译

Cython 是一种语言的编译器,这种语言就是 Python 加上可选的 C 类型声明。你编写 .pyx 文件,Cython 把它翻译成 C,C 编译器再把它变成扩展模块,然后像其他模块一样 import。用这种方式编译的普通 Python 代码已经略快一些,但真正的提升来自声明类型:cdef double total 告诉 Cython total 是机器数字而不是 Python 对象,于是循环直接在原始数字上运行,没有解释器开销。繁重的数值循环可以变快五十到一百倍,与 Numba 类似,但你得到的是一个可以发布的普通编译模块。

# What a Cython file looks like: Python with cdef type declarations (shown here as text)
pyx = """\
cpdef double dot(double[:] a, double[:] b):
    cdef double total = 0.0
    cdef long i
    for i in range(a.shape[0]):
        total += a[i] * b[i]
    return total
"""
print(pyx.splitlines()[0])                           # cpdef double dot(double[:] a, double[:] b):
print(sum("cdef" in line for line in pyx.splitlines()))     # 2   two typed declarations inside the function

声明类型:cdef、cpdef 与内存视图

cdef 声明 C 变量(cdef double total、cdef long i)或只能由 C 代码调用的函数。cpdef 声明既能从 Python 调用也能从 C 调用的函数,适合作为模块的入口点。def 仍是普通的 Python 函数。数组以类型化内存视图传入,例如一维 double 数组用 double[:]:它无需拷贝就能接收 NumPy 数组,并以 C 的速度做下标访问。因此像 cpdef double mean(double[:] x, long n) 这样的签名,把 Python 的动态类型换成了精确类型,而被省略或声明为 object 的类型仍是缓慢的 Python 对象。

# Building the declarations as text: one table from Python type names to C types
C_TYPES = {"int": "long", "float": "double", "bool": "bint", "array[float]": "double[:]"}

def c_type(py_type):
    return C_TYPES.get(py_type, "object")

def cdef_signature(name, params, returns):
    result = "void" if returns is None else c_type(returns)
    arguments = ", ".join(f"{c_type(t)} {p}" for p, t in params)
    return f"cpdef {result} {name}({arguments})"

print(cdef_signature("mean", [("x", "array[float]"), ("n", "int")], "float"))    # cpdef double mean(double[:] x, long n)
print(cdef_signature("log_event", [("message", "dict")], None))                  # cpdef void log_event(object message)

构建模块与找出该加类型的位置

带有 cythonize(["pricing.pyx"]) 的 setup.py 会让 setuptools 翻译并编译该文件;python setup.py build_ext --inplace 会在你的代码旁生成共享库,import pricing 就能加载它。快速实验可以用 pyximport,它在导入时编译。要看哪里仍然慢,运行 cython -a pricing.pyx:它会生成一个 HTML 页面,每一行按使用的 Python 机制多少涂上黄色,白色的行是纯 C。给热点循环中黄色行里的变量加上类型,然后用 @cython.boundscheck(False) 和 @cython.wraparound(False) 关掉你已证明不需要的检查,让下标访问像 C 一样便宜。

# The build script is a few lines of text; this generates it for a list of modules
def build_setup(modules):
    files = ", ".join(f'"{module}.pyx"' for module in modules)
    return "from setuptools import setup\nfrom Cython.Build import cythonize\n\n" + f"setup(ext_modules=cythonize([{files}]))\n"

print(build_setup(["pricing", "risk"]), end="")
# from setuptools import setup
# from Cython.Build import cythonize
#
# setup(ext_modules=cythonize(["pricing.pyx", "risk.pyx"]))
# The loop a .pyx file would hold, as plain Python: same result, compiled once types are added
def dot(a, b):
    total = 0.0
    for i in range(len(a)):
        total += a[i] * b[i]
    return total

prices = [184.25, 410.5, 99.0]
quantities = [100.0, 50.0, 200.0]
print(dot(prices, quantities))                       # 58750.0  the compiled version returns exactly this number
print(dot(prices, quantities) == sum(p * q for p, q in zip(prices, quantities)))     # True
用 Cython、Numba,还是都不用?Numba 是更轻量的选择:一个装饰器,无需构建步骤,适合数值函数。Cython 需要一个构建步骤和第二种文件类型,但能得到可发布的普通模块,没有 JIT 启动开销,还能调用 C 库、自由混合 C 与 Python 代码。先尝试 NumPy 向量化,再 Numba,再 Cython;无论哪种,在信任之前都要把输出与朴素的 Python 版本对比。
  • Cython 把 .pyx 文件(Python 加可选的 C 类型)编译成 C,再编译成可正常 import 的扩展模块。
  • cdef 声明 C 变量与仅限 C 的函数,cpdef 声明可由 Python 和 C 调用的函数;数组是 double[:] 这样的类型化内存视图。
  • 在 setup.py 中用 cythonize 构建,用 cython -a 找出热点循环里的黄色(慢)行,并与朴素 Python 的结果核对。

下一节:用 pybind11 调用 C++,把现有的 C++ 代码暴露给 Python。

实验:评分内容

  • 用 c_type 把 Python 类型映射到 C25 分
  • 用 cdef_signature 写出函数头25 分
  • 用 declare_locals 给局部变量加类型25 分
  • 用 build_setup 写构建脚本25 分
资源管理器 · lab-123-1
▾ lab-123-1
…
bash · 浏览器内
learner@quantcraft:~/lab-123-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

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

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

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

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

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