Mojo 控制流语句详解:if、while、for、短路求值与仓库示例代码的运行验证
【免费下载链接】mojoThe Modular Platform (includes MAX & Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo
本篇围绕 Mojo 手册的 Control flow(控制流)章节及其配套示例目录 control-flow 展开,完整覆盖if/elif/else、短路求值、条件表达式、while与for循环(含break/continue/else子句、引用迭代、range 生成器及 Python 集合迭代)的全部语法要点与可运行代码。读完后,你将掌握 Mojo 条件与循环语句的完整用法,并能直接通过仓库自带的 Bazel 示例目标复现、验证每一段代码的输出行为。
一、目录定位:手册章节的示例代码与测试入口
示例目录 Mojo/docs/site/code/manual/control-flow 是 Mojo 手册 Control flow 章节 的代码示例与测试载体,其 README 明确说明了两点组织约定:
- 目录中每个
.mojo文件都是一个独立的 Mojo 应用程序; - 目录中的 BUILD.bazel 为每个
.mojo文件定义了:- 一个
mojo_binary目标(目标名取文件名去掉扩展名); - 一个
modular_run_binary_test目标(以_test为后缀)。
- 一个
当前目录中的示例文件与对应主题的一一对应关系如下:
| 示例文件 | 对应手册小节 |
|---|---|
| if_statement.mojo | if语句(基础、单行、elif/else) |
| short_circuit.mojo | 短路求值(or/and) |
| conditional_expression.mojo | 条件表达式(三元运算符) |
| while_loop.mojo | while语句(含break/continue/else) |
| for_loop.mojo | for语句(集合、range、引用迭代、Python 集合) |
构建与测试目标的细节
control-flow 的 BUILD.bazel 通过 glob 收集全部.mojo源文件,并用列表推导批量生成目标:
load("//bazel:api.bzl", "modular_run_binary_test", "mojo_binary") package(default_visibility = ["//oss/modular/docs:__subpackages__"]) MOJO_SRCS = glob(["*.mojo"]) [ mojo_binary( name = src.split(".")[0], srcs = [src], deps = [ "@mojo//:std", ], ) for src in MOJO_SRCS ] [ modular_run_binary_test( name = src.split(".")[0] + "_test", size = "small", binary = src.split(".")[0], target_compatible_with = select({ "//:asan": ["@platforms//:incompatible"], "//conditions:default": [], }), ) for src in MOJO_SRCS ]几个值得注意的实现细节:
- 每个二进制的唯一依赖是
@mojo//:std,即 Mojo 标准库,说明这些示例只依赖语言内建能力,不需要额外第三方库; - 测试目标通过
target_compatible_with在开启asan(AddressSanitizer)配置时标记为不兼容,即//:asan构建下会自动跳过这些运行时测试; - 包可见性声明为
//oss/modular/docs:__subpackages__,从源码结构看,该目录在 Bazel 工作区中的包映射归属于oss/modular/docs子树,测试目标命名统一为<文件名>_test(例如if_statement_test、for_loop_test),可用bazel test按标签运行以验证程序能否成功构建并执行。
二、if语句:条件执行、单行形式与 elif/else 链
Mojo 支持if语句用于条件代码执行:当给定的布尔表达式求值为True时,执行其后的缩进代码块。示例来自 if_statement.mojo:
var temp_celsius = Float64(25) if temp_celsius > 20: print("It is warm.") print("The temperature is", temp_celsius * 9 / 5 + 32, "Fahrenheit.")输出:
It is warm. The temperature is 77.0 Fahrenheit.注意示例中temp_celsius显式使用Float64(25)构造,条件比较>与浮点算术均按Float64语义执行,因此华氏温度输出为77.0而非整数。
如果只需条件执行单条短语句,整个if语句可以写成一行:
temp_celsius = 22 if temp_celsius < 15: print("It is cool.") # Skipped because condition is False if temp_celsius > 20: print("It is warm.")输出:
It is warm.第一条单行if因条件为False被跳过,第二条命中并执行。
if语句还可以包含任意多个elif子句,每个子句给出一个布尔条件与对应代码块。条件按书写顺序依次测试,一旦某个条件为True,执行其代码块后不再测试后续条件;else子句则在所有条件均为False时执行:
temp_celsius = 25 if temp_celsius <= 0: print("It is freezing.") elif temp_celsius < 20: print("It is cool.") elif temp_celsius < 30: print("It is warm.") else: print("It is hot.")输出:
It is warm.手册中同时明确了一处与 Python/C 的差异:Mojo不支持等价于 Pythonmatch或 Cswitch的匹配/分支语句,多分支判断统一使用elif链表达。
短路求值(Short-circuit evaluation)
Mojo 对布尔运算符遵循短路求值语义:
or运算符:若第一个操作数求值为True,则不求值第二个操作数;and运算符:若第一个操作数求值为False,则不求值第二个操作数。
short_circuit.mojo 通过两个带副作用(打印日志)的函数直观验证了这一点:
# Short-circuit "or" evaluation def true_func() -> Bool: print("Executing true_func") return True def false_func() -> Bool: print("Executing false_func") return False print('Short-circuit "or" evaluation') if true_func() or false_func(): print("True result") # Short-circuit "and" evaluation print('Short-circuit "and" evaluation') if false_func() and true_func(): print("True result")输出:
Short-circuit "or" evaluation Executing true_func True result Short-circuit "and" evaluation Executing false_funcor表达式中true_func()返回True后,false_func()从未被执行(没有 "Executing false_func" 日志);and表达式中false_func()返回False后,true_func()同样未被执行。该示例也表明短路求值在避免不必要的函数调用与副作用方面是可观察、可测试的行为。
条件表达式(三元运算符)
Mojo 支持与 Python 相同语法形式的条件表达式:<true_result> if <boolean_expression> else <false_result>,最常见的用法是根据布尔条件为变量赋值两个候选值之一。conditional_expression.mojo 同时展示了表达式形式与等价的if语句形式:
var temp_celsius = 15 var forecast = "warm" if temp_celsius > 20 else "cool" print("The forecast for today is", forecast) # Equivalent if-else statement if temp_celsius > 20: forecast = "warm" else: forecast = "cool" print("The forecast for today is", forecast)输出(两次):
The forecast for today is cool The forecast for today is cool与示例文件对应的写法差异值得注意:示例文件中直接复用第一次声明的forecast变量(var forecast = "warm" if ... else "cool"),随后在等价if语句中重新赋值;而手册中的等价写法则先做类型声明var forecast: String再分支赋值。两种方式语义一致,前者更简洁,后者在变量需要在两个分支中首次绑定时更清晰。
三、while语句:循环、break/continue 与 else 子句
while循环在布尔表达式为True期间重复执行代码块。while_loop.mojo 用一个 Fibonacci 数列生成器展示了最基本的while用法——输出所有小于 50 的 Fibonacci 数:
var fib_prev = 0 var fib_curr = 1 print(fib_prev, end="") while fib_curr < 50: print(",", fib_curr, end="") fib_prev, fib_curr = fib_curr, fib_prev + fib_curr输出:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34注意循环体中的元组解包赋值fib_prev, fib_curr = fib_curr, fib_prev + fib_curr,一行完成两个变量的同步更新,这是 Mojo 语法的一个常用习惯。
continue与break
continue跳过代码块剩余部分,直接从循环测试表达式继续;break立即终止循环。
var n = 0 while n < 5: n += 1 if n == 3: continue print(n, end=", ")输出:
1, 2, 4, 5,n = 0 while n < 5: n += 1 if n == 3: break print(n, end=", ")输出:
1, 2,while的else子句:何时执行、何时不执行
while循环可选地携带else子句:当循环条件求值为False时执行else块,即使这是条件第一次被测试(即循环体一次都不执行时,else依然执行):
var n = 5 while n < 4: print(n) n += 1 else: print("Loop completed")输出:
Loop completed关键限制是:如果循环因break或return退出,else子句不执行:
n = 0 while n < 5: n += 1 if n == 3: break print(n) else: print("Executing else clause")输出:
1 2break在n == 3时触发,因此 "Executing else clause" 不会打印。这一 "正常结束才执行 else" 的语义与 Python 一致,是while循环中实现"带哨兵值的处理"的惯用结构。
四、for语句:集合迭代、range、引用修改与 Python 集合
Mojo 的for循环遍历序列并为每个元素执行代码块。其迭代协议是:被迭代类型需实现__iter__()方法,返回的对象需定义__next__()和__len__()方法。for_loop.mojo 是该主题最完整的示例,下面按小节拆解。
4.1 迭代 Mojo 集合
标准库collections模块中的所有集合类型都支持for迭代。
List——直接遍历元素:
var states: List[String] = ["California", "Hawaii", "Oregon"] for state in states: print(state)输出:
California Hawaii OregonSet——无序集合,遍历顺序由集合自身决定:
var numbers = {42, 0} for number in numbers: print(number)输出:
42 0Dict有两种迭代技术。第一种是直接遍历Dict,产生的是键序列:
var capitals: Dict[String, String] = { "California": "Sacramento", "Hawaii": "Honolulu", "Oregon": "Salem", } for var state in capitals: print(t"{capitals[state]}, {state}")输出:
Sacramento, California Honolulu, Hawaii Salem, Oregon第二种是调用items()方法,产生DictEntry对象序列,在循环体内通过key与value字段访问:
for item in capitals.items(): print(t"{item.value}, {item.key}")输出:
Sacramento, California Honolulu, Hawaii Salem, Oregon两种技术输出相同,区别在于:直接迭代只能拿到键(取值需二次下标查找),而items()每轮同时暴露键值对,避免了额外查找,是更推荐的写法。
4.2 引用迭代:在循环中修改集合元素
Mojo 的集合迭代器返回的是引用,默认被不可变地捕获到循环变量中。若需要拿到可变元素引用,需在循环变量前加ref关键字,建立与元素引用匹配的引用绑定——这在循环内修改集合内容时非常有用。示例见 for_loop.mojo 第 78-83 行:
var values: List[Int] = [1, 4, 7, 3, 6, 11] for ref value in values: if value % 2 != 0: value -= 1 print(values)输出:
[0, 4, 6, 2, 6, 10]所有奇数元素(1、7、3、11)各减一,偶数元素保持不变。若不加ref,循环变量是值的不可变副本,对它的修改不会影响原列表。
4.3 迭代 range:惰性生成整数序列
标准库提供的另一种可迭代类型是range——由range()函数生成的整数序列。它与前面集合类型的本质区别在于:range 以**生成器(generator)**方式实现,按需逐个产生值,而不在内存中物化整个序列。
for i in range(5): print(i, end=", ")输出:
0, 1, 2, 3, 4,因此for i in range(大数)形式的循环不会消耗额外内存,适合数值型循环场景。
4.4for循环控制语句
continue跳过本轮剩余代码并继续下一个元素:
for i in range(5): if i == 3: continue print(i, end=", ")输出:
0, 1, 2, 4,break终止循环:
for i in range(5): if i == 3: break print(i, end=", ")输出:
0, 1, 2,for循环同样可选地携带else子句,在遍历完所有元素后执行——即使集合为空也会执行:
for i in range(5): print(i, end=", ") else: print("\nFinished executing 'for' loop")输出:
0, 1, 2, 3, 4, Finished executing 'for' loop空集合场景:
var empty = List[Int]() for i in empty: print(i) else: print("Finished executing 'for' loop")输出:
Finished executing 'for' loop与while相同的规则:break或return终止for循环时,else子句不执行:
var animals: List = ["cat", "aardvark", "hippopotamus", "dog"] for animal in animals: if animal == "dog": print("Found a dog") break else: print("No dog found")输出:
Found a dog这一语义使for...else成为"查找是否成功"的天然模式:else分支等价于"遍历结束仍未 break"。
4.5 迭代 Python 集合
Mojo 的for循环还支持迭代 Python 集合类型,每轮取出的元素都是PythonObject包装器。
迭代混合类型 Python 列表:
from std.python import Python def main() raises: # Create a mixed-type Python list var py_list = Python.list(42, "cat", 3.14159) for py_obj in py_list: # Each element is of type "PythonObject" print(py_obj)输出:
42 cat 3.14159对 Python 字典同样有两种技术。第一种直接迭代得到键序列(键与值均为PythonObject):
from std.python import Python def main() raises: # Create a mixed-type Python dictionary var py_dict = Python.evaluate("{'a': 1, 'b': 2.71828, 'c': 'sushi'}") for py_key in py_dict: # Each key is of type "PythonObject" print(py_key, py_dict[py_key])第二种是调用items(),得到 2-元组序列,通过下标访问键值:
from std.python import Python def main() raises: var py_dict = Python.evaluate("{'a': 1, 'b': 2.71828, 'c': 'sushi'}") for py_tuple in py_dict.items(): # Each 2-tuple is of type "PythonObject" print(py_tuple[0], py_tuple[1])两者输出相同:
a 1 b 2.71828 c sushi两个值得注意的边界细节:其一,涉及Python.evaluate等 Python 互操作调用时,main需要声明raises以允许异常传播,for_loop.mojo 即如此;其二,PythonObject是动态类型包装,因此示例中对键值只做了打印与下标访问,而不能施加静态类型约束——这是 Mojo 静态类型与 Python 动态类型边界处的固有特性。
五、语义速查:break 对 else 子句的影响
| 构造 | 正常结束 | 空/首次条件即假 | break退出 | return退出 |
|---|---|---|---|---|
while...else | 执行else | 执行else | 不执行else | 不执行else |
for...else | 执行else | 执行else | 不执行else | 不执行else |
这一对照表由 while_loop.mojo 与 for_loop.mojo 中的四组对照示例(n = 5; while n < 4、空List[Int]、n == 3处break、animal == "dog"处break)共同验证。
六、在仓库中验证这些示例
上述行为均不是纸面描述,仓库通过 Bazel 为每个示例提供了可执行验证:
- 构建/运行:每个
.mojo文件对应一个mojo_binary目标(如if_statement、while_loop),依赖仅为@mojo//:std; - 测试:每个二进制对应一个
modular_run_binary_test目标(如if_statement_test、for_loop_test,size = "small"),通过运行二进制本身验证程序可构建、可执行; - 注意
//:asan构建配置下这些测试目标被标记为不兼容并自动排除,因此验证应在默认(非 ASan)配置下进行; - 由于包可见性限定为
//oss/modular/docs:__subpackages__,从源码结构看该目录在 Bazel 工作区中归属于oss/modular/docs包树,实际bazel test标签前缀以当前工作区的包映射为准。
七、小结
本文完整继承了 Control flow 手册章节 的语义规定,并以 control-flow 示例目录 中五个独立可运行的.mojo程序为佐证:
if支持单行形式与任意长度的elif链,无switch/match等价物;and/or严格短路求值,可通过副作用观察验证;- 条件表达式提供与
if语句等价的三元赋值形式; while与for均支持break/continue/else子句,else仅在"非 break/return 退出"时执行,空序列或首次条件为假时依然执行;for可迭代List/Set/Dict(含items()双技术)、惰性range()、以及PythonObject包装的 Python 集合;ref循环绑定是唯一能在循环内修改集合元素的机制。
所有示例源码、构建规则与预期输出均可在 Mojo/docs/site/code/manual/control-flow 目录中直接查阅与复现。
【免费下载链接】mojoThe Modular Platform (includes MAX & Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考