Chapter 02

Python 兼容性

在 Mojo 中无缝使用 Python 代码与库,理解两者互操作的原理和性能影响

Python 代码直接在 Mojo 中运行

Mojo 是 Python 的超集

Mojo 最重要的设计决策之一:它是 Python 的严格超集。这意味着合法的 Python 代码在 Mojo 中也是合法的(绝大多数情况下)。你不需要重写现有的 Python 代码就可以在 Mojo 环境中运行。

# 这是合法的 Mojo 代码,也是合法的 Python 代码
def greet(name):
    return f"Hello, {name}!"

# 列表推导式
squares = [x**2 for x in range(10)]
print(squares)

# 字典操作
data = {"model": "GPT-4", "tokens": 8192}
for key, value in data.items():
    print(f"{key}: {value}")

# 类定义(Python 风格)
class Model:
    def __init__(self, name: str):
        self.name = name

    def predict(self, x):
        return x * 2

def main():
    m = Model("my-model")
    print(m.predict(5))  # 10

from python import:导入 Python 库

Mojo 内置了 Python 解释器集成,可以直接导入任何已安装的 Python 库。Mojo 使用 from python import 语法启动嵌入式 CPython 解释器,加载 Python 模块。

from python import Python

def main():
    # 导入 NumPy
    var np = Python.import_module("numpy")

    # 创建 NumPy 数组
    var arr = np.array([1.0, 2.0, 3.0, 4.0])
    print(arr)           # [1. 2. 3. 4.]
    print(arr.mean())    # 2.5

    # 矩阵运算
    var mat = np.random.rand(3, 3)
    var result = np.dot(mat, mat)
    print(result)

PythonObject 类型

什么是 PythonObject?

当你在 Mojo 中使用 Python 对象时,它们被包装为 PythonObject 类型。这个类型是 Mojo 和 Python 运行时之间的桥梁,负责引用计数、类型转换和调用约定的适配。

from python import Python, PythonObject

def main():
    # PythonObject 可以持有任何 Python 值
    var py_int: PythonObject = 42
    var py_str: PythonObject = "hello"
    var py_list: PythonObject = [1, 2, 3]

    # 调用 Python 方法
    print(py_str.upper())     # HELLO
    print(py_list.__len__()) # 3

    # 从 Python 对象转换为 Mojo 原生类型
    var mojo_int: Int = py_int.to_int()
    var mojo_str: String = String(py_str)
    print(mojo_int + 10)   # 52(Mojo 原生 Int 运算)

PythonObject 的性能含义

使用 PythonObject 意味着你在走 Python 的动态类型路径,所有操作都经过 CPython 解释器,不能获得 Mojo 的编译期优化。这是设计取舍:互操作性 vs 性能。

何时用 PythonObject,何时用 Mojo 原生类型

用 PythonObject:调用 Python 库(NumPy/PyTorch/Pandas)、对接现有 Python 代码、处理动态数据结构。
用 Mojo 原生类型:热路径计算、SIMD 操作、性能敏感代码。
最佳实践:在 Python 生态中准备数据,交给 Mojo 原生代码进行高速计算,结果再传回 Python。

调用主流 Python 库

NumPy:数值计算

from python import Python

def numpy_demo():
    var np = Python.import_module("numpy")

    # 创建数组并做矩阵乘法
    var A = np.random.rand(1000, 1000)
    var B = np.random.rand(1000, 1000)
    var C = np.dot(A, B)    # NumPy 内部用 BLAS,已经很快
    print("结果形状:", C.shape)

    # 统计运算
    print("均值:", C.mean())
    print("标准差:", C.std())

def main():
    numpy_demo()

Pandas:数据处理

from python import Python

def pandas_demo():
    var pd = Python.import_module("pandas")

    # 读取 CSV 文件
    var df = pd.read_csv("data.csv")
    print(df.head())
    print(df.describe())

    # 数据过滤
    var filtered = df[df["score"] > 0.8]
    print("高分样本数量:", len(filtered))

实战:在 Mojo 中调用 PyTorch

from python import Python

def pytorch_inference():
    var torch = Python.import_module("torch")
    var torchvision = Python.import_module("torchvision")

    # 加载预训练 ResNet-50
    var models = torchvision.import_module("models")
    var model = models.resnet50(pretrained=True)
    model.eval()

    # 创建随机输入张量(batch_size=1, channels=3, 224x224)
    var dummy_input = torch.randn(1, 3, 224, 224)

    # 推理(不计算梯度)
    with torch.no_grad():
        var output = model(dummy_input)

    # output.shape: [1, 1000](ImageNet 1000 类)
    var predicted_class = output.argmax(dim=1)
    print("预测类别 ID:", predicted_class)

def main():
    pytorch_inference()

逐步迁移策略:Python → Mojo

迁移的四个阶段

不需要一次性重写所有代码。推荐的迁移路径是渐进式的,按性能影响从大到小排序:

阶段 1

直接运行 Python 代码(零迁移成本)

.py 改名为 .mojo,用 Mojo 解释器运行。性能与 Python 相同,但你已经在 Mojo 环境中了。

阶段 2

关键函数加类型注释(10-100x 加速)

def 改为 fn,添加参数类型和返回类型声明。Mojo 编译器可以立即做大量优化。

阶段 3

使用 var/let 声明变量(减少动态开销)

var 声明可变变量,用 let 声明不可变变量,帮助编译器做别名分析和优化。

阶段 4

SIMD + 并行化热路径(100-10000x 加速)

对计算密集的内循环使用 vectorizeparallelize,这是性能提升最大的阶段。

实际迁移示例

# 原始 Python 代码
def dot_product(a, b):
    result = 0.0
    for i in range(len(a)):
        result += a[i] * b[i]
    return result

# ~500ms for 10M elements
# 迁移后的 Mojo 代码
fn dot_product(
    a: List[Float64],
    b: List[Float64]
) -> Float64:
    var result: Float64 = 0.0
    for i in range(len(a)):
        result += a[i] * b[i]
    return result

# ~5ms for 10M elements (100x faster)

Python 互操作的性能影响

性能边界在哪里?

理解 Mojo 和 Python 的性能边界对于写出高效代码至关重要。每次从 Mojo 调用 Python 函数,都会经历以下开销:

Python 调用链开销: Mojo 代码 ↓ (类型转换 Mojo 类型 → PythonObject) CPython 解释器 ↓ (动态类型查找、字节码执行) Python 函数执行 ↓ (结果转换 PythonObject → Mojo 类型) 回到 Mojo 代码 跨越 Mojo/Python 边界:每次约 1-10 微秒开销 在循环内频繁跨越边界 → 性能灾难 解决方案:批量处理,减少跨越次数
黄金法则:批量操作,最小化边界跨越

不要在 Mojo 循环内逐个元素调用 Python 函数。应该:在 Mojo 侧准备好整个数据批次,一次性传给 NumPy/PyTorch,用向量化操作处理,结果一次性返回给 Mojo。

本章小结

Python 超集:Python 代码可以直接在 Mojo 中运行,无需修改。
PythonObject:Mojo 中 Python 对象的包装类型,提供完整 Python 动态特性但不享受编译期优化。
from python import:通过嵌入式 CPython 解释器导入任意 Python 库。
性能影响:跨越 Mojo/Python 边界有开销,批量操作可以最小化这种影响。
迁移策略:def → fn → var/let → SIMD,渐进式提升性能,而非一次性重写。