Mojo 语法要点:Python 外衣下的系统语言

要点

前置阅读是开山篇:Mojo 的定位、pixi 安装路线与 Python 互操作全景已在那边讲过,本篇只讲语言核心。截至本文(2025 年 6 月下旬),Mojo 最新版是 6 月 18 日发布的 25.4,语言仍在快速演进。”长得像 Python 的系统语言”这句话里,藏着三处最容易按 Python 心智误读的地方:两套函数声明(def/fn)怎么分工、编译期与运行时是两个世界(struct/SIMD/alias)、所有权写在参数签名里(read/mut/owned)。本文按这三条线过掉高频误解,所有示例以 25.4 时点的语法为准。

def 与 fn:两套声明不是冗余,是分工

Mojo 同时保留 Python 式的 def 和严格模式 fn。这不是过渡期的兼容措施,而是刻意设计:脚本和胶水代码用 def 保持 Python 手感,性能关键路径用 fn 换来编译期的全部能力。两者的差异是硬性的,不是风格偏好:

def total(xs):                 # def:动态模式
    t = 0.0                    # 变量可以隐式声明,像 Python
    for x in xs:
        t += x
    return t

fn total_strict(xs: List[Float64]) -> Float64:   # fn:严格模式
    var t = 0.0                # 变量必须用 var 显式声明
    for x in xs:
        t += x
    return t

两条关键差异:def 的参数是可变副本——函数内随便改,不影响调用者,值语义符合直觉;fn 的参数默认是只读借用(read)——你拿到的是原值的引用,语言规定你只能读不能写。在 fn 里对参数赋值会直接编译报错:

fn bump(x: Int) -> Int:
    x += 1        # 编译错误:fn 参数默认按 read 借用,只读
    return x

反模式点名:报错后随手把参数改成 owned 来”消掉错误”。owned 的语义是接管所有权,调用方交出这个值——为了改一个整数就转移所有权,是把语义工具当报错消除器用。正确动作是想清楚意图:只读就保持默认,要改写就标 mut,见下一节。

参数约定:所有权写在签名里

fn 的每个参数可以带一个约定(argument convention),告诉编译器这个参数怎么进入函数体。这组关键字 2025 年起(25.1 起)统一为 read/mut/owned,构造器里的 self 用 out:

约定 语义 典型场景
(默认,read) 只读借用,函数内不可改,无拷贝开销 绝大多数只读参数
mut 可变借用,改动对调用者可见 就地修改:填充、累加、交换
owned 接管所有权,调用方交出值 消耗值、转移缓冲区、链式变换
out 结果参数,函数负责把值”写出来” __init__(out self) 与返回值复用
fn bump(mut counter: Int):    # mut:可变借用
    counter += 1              # 改动写回调用者的变量

fn main():
    var n = 3
    bump(n)
    print(n)                  # 4

读旧教程要会换算:24.x 时代的 borrowed 就是现在的默认 read,inout 现在叫 mut。还要意识到这组命名本身仍在打磨期——2025 年内已经换过一轮,具体拼写以官方文档为准。

struct:静态类型的载体,不是 Python class

struct 是 Mojo 的静态类型载体:字段与方法在编译期确定,内存布局与 C 结构体同量级。它和 Python class 有两条本质差异——没有继承,字段必须标类型。方法里的 self 同样遵守参数约定:要改字段就把 self 标成 mut,只读方法保持默认借用:

@fieldwise_init   # 自动生成逐字段构造器(旧教程的 @value 是它的前身,已弃用)
struct Accumulator:
    var total: Float64
    var count: Int

    fn add(mut self, v: Float64):   # 要写字段,self 必须是 mut
        self.total += v
        self.count += 1

    fn mean(self) -> Float64:       # 只读方法,self 用默认借用
        if self.count == 0:
            return 0.0
        return self.total / self.count

    fn __init__(out self):          # out self:构造器负责把 self 写出来
        self.total = 0.0
        self.count = 0

fn main():
    var acc = Accumulator()
    acc.add(1.5)
    acc.add(2.5)
    print(acc.mean())               # 2.0

复用不靠继承靠什么?靠 trait:结构体名后括号里声明遵循哪些 trait(如 Copyable、Movable、Stringable),并实现对应方法。哪些 trait 需要显式声明、哪些自动继承,25.x 还在调整,以官方文档为准。反模式点名:拿 Python class 的心智给 struct 找”父类”——Mojo 没有这条通路,强行模仿只会写出既没继承又没 trait 的四不像。

SIMD:连标量都是向量

Mojo 最”系统语言”的一面在这里:浮点标量不是基本类型,而是宽度为 1 的 SIMD 向量的别名——Float64 即 SIMD[DType.float64, 1]。向量宽度是编译期参数,写在方括号里:

fn main():
    var a = SIMD[DType.float32, 4](1.0, 2.0, 3.0, 4.0)
    var b = SIMD[DType.float32, 4](10.0, 20.0, 30.0, 40.0)

    print(a * b)             # 逐通道相乘,一条指令干四个
    print(a.reduce_add())    # 归约成一个标量
    print(a[2])              # 取单个通道

这个设计的意义在于:向量化不是”调用某个加速库”,而是把类型宽度从 1 改成 4 或 8 的参数化改写。同时别误读——Int 是独立的整数类型,不在 SIMD 体系里,浮点才有这个别名待遇。

alias 与 @parameter if:把计算搬进编译期

alias 声明编译期常量:使用处直接替换为编译期求值结果。@parameter if 是编译期分支:条件在编译期定型后,不成立的那一支整体消失,运行时不存在这次判断:

alias WIDTH = 4          # 编译期常量,不是运行时变量

fn chosen() -> Int:
    @parameter if WIDTH > 2:
        return WIDTH     # 编译期分支:运行时没有这个 if
    else:
        return 1

fn main():
    print(chosen())      # 4

反模式点名:用 var 装向量宽度、矩阵形状这类”结构性数值”。SIMD 的宽度参数、struct 的参数化(如 SIMD[DType.float64, W])只接受编译期值,var 装的值进不了方括号。运行时数据用 var,编译期形状用 alias——这个分界线贯穿 Mojo 全部元编程。官方仓库里关于 alias 命名的讨论已挂了多年,语义方向不变、拼写可能变,以官方文档为准。

控制流与错误处理:这部分真的像 Python

前几节都是”像 Python 但不一样”,控制流与错误处理是少数可以直接照搬 Python 直觉的地方:if/elif/else、while、for ... in、break/continue、raise 与 try/except 全部在场,条件不写括号,代码块用缩进。差异集中在两处:循环对象必须是可迭代的 Mojo 类型(range、List、Dict 都行),以及 fn 里抛错要在签名声明 raises:

构造 主场 一句话边界
if / elif / else 布尔分支 与 Python 语义一致,条件不用括号
while 不定次数循环 条件是运行时布尔,别和 @parameter if 混为一谈
for … in 遍历可迭代对象 range(start, stop, step) 三参形式可用
raise / try / except 错误传播与兜底 fn 抛错必须签名写 raises;def 可隐式
fn find_first_gt(xs: List[Float64], t: Float64) raises -> Int:
    for i in range(len(xs)):
        if xs[i] > t:
            return i
    raise Error("no element above threshold")

这节的实用结论是:从 Python 迁移脚本骨架(读循环、分支、报错)几乎零成本,真正的学习成本全部集中在参数约定、struct 与编译期那一层——这也正是 Mojo 与 Python 分工的意义,胶水部分别浪费精力重新学。

下一步

语法骨架过完了。要一份能查的索引(每个关键字、常用类型一条:语义、最小示例、坑),读《Mojo 基础知识参考字典》;要把这些语法用到真实场景——按”朴素实现到 SIMD 并行”四步加速矩阵乘法的可复现实验设计——读《用 Mojo 加速矩阵乘法》。对照着理解 def/class 这组差异的 Python 侧,可看站内《Python 函数与面向对象》。

版本核实(以官方文档为准):Mojo 25.4 发布于 2025-06-18(YY.N 版本方案);本文涉及的 def/fn 双模式、参数约定 read/mut/owned 与构造器 out(25.1 起定名,24.x 的 borrowed/inout 为旧名)、@fieldwise_init(@value 已弃用)、struct 无继承、trait 括号声明、Float64 为宽度 1 的 SIMD 别名、alias 与 @parameter if、fn 抛错需签名声明 raises,均为该时点已发布的语言事实;trait 自动继承的具体范围与 alias 的后续命名演进仍在调整中,具体以 docs.modular.com 当前文档为准。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇