Mojo 可变参数Variadics完整指南从参数列表、VariadicList 到 VariadicPack 与关键字参数【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo本指南以 Mojo/proposals/variadics-design.md 为骨架系统讲解 MojoModular 平台中可变参数特性的设计、语法与底层实现编译期可变参数列表TypeList/ParameterList、运行时同构可变参数VariadicList、异构可变参数包VariadicPack以及可变关键字参数StringDict设计文档中写作OwnedKwargsDict。读完本文你将掌握如何在函数签名中使用*args、*args: *Ts与**kwargs理解其借用/所有权语义、低层 lowering 方式并能结合标准库源码与测试用例写出惯用的元编程与转发代码。概述Mojo 可变参数的四大形态Mojo 的可变参数variadic能力横跨编译期与运行期两条主线形态语法示例背后类型作用域编译期类型列表*Ts: AnyTypeTypeListdef/struct/comptime参数位编译期值列表*elts: IntParameterList同上运行时同构参数*args: IntVariadicList运行时调用点运行时异构参数包*args: *TsVariadicPack运行时调用点运行时关键字参数**kwargs: IntStringDict[V]文档中的OwnedKwargsDict[V]运行时调用点这些特性统一采用 Python 风格的语法*args与**kwargs对熟悉 Python 的用户有天然的亲切感。编译期参数列表把静态已知数量的元素打包进一个绑定运行时同构参数允许单个参数位接收任意数量的同形状实参异构参数包用*args: *Ts语法把一组可变参数与一组可变类型一一对应关键字参数则收集未被命名参数消费的keyvalue操作数。本文档当前状态为 Draft是面向语言/标准库贡献者与进阶用户的实现级参考。本文所有示例均可对照标准库测试 Mojo/stdlib/test/builtin/test_variadic.mojo该文件是语法最新真相的来源与核心实现 Mojo/stdlib/std/builtin/variadics.mojo。Variadic parameter lists编译期参数列表可变参数parameter列表出现在编译期参数位置def、struct、comptime等声明上把静态已知数量的元素捆绑进一个绑定。与 Python 相同Mojo 用前导*表示一组值或类型def takes_types[*Ts: AnyType](): ... def takes_values[*elts: Int](): ...Mojo 严格区分这两种情况标准库分别以TypeList与ParameterList呈现均定义在 Mojo/stdlib/std/builtin/variadics.mojo。两者建立在同一个 KGEN 概念之上一个 MLIR!kgen.param_list值其元素共享同一种编译期形态要么全部满足某个 trait 的类型要么全部是同一种类型的值。它们是内建类型正常使用无需显式导入——模块头部注释即声明These are Mojo built-ins, so you dont need to import them.TypeList类型序列的编译期操作类型列表绑定一系列类型声明时使用 trait 约束而非值类型例如*Ts: AnyType或*Ts: Movable。编译器通过TypeList操作将其暴露出来对上面的示例type_of(Ts)就是TypeList。从源码看TypeList底层类型正是_MLIR.KGENParamListType[Self.Trait]即!kgen.param_listTrait见 variadics.mojo并借助#kgen.param_list.size、#kgen.param_list.get等 MLIR 属性实现查询与索引。TypeList提供一系列实用操作编译期size/length元素个数如tl.length定长索引Ts[i]可用于comptime循环通过__getitem_param__[idx]实现构造与变换助手TypeList.of、splat、tabulate、map、reduce、filter_idx、contains、reversed、slice等。典型用途包括对多个类型参数做 trait 谓词判断参见 Mojo/stdlib/std/traits/movable.mojo以及遍历/变换类型包的元编程。测试用例 test_variadic.mojo 中的test_type_list_map_to_values、test_type_list_filter_idx_*、test_type_list_reduce_idx等覆盖了这些操作的常见组合。ParameterList同类型编译期值序列值列表绑定一系列共享同一类型T的编译期值例如*args: Int或*names: String。它变成一个元素类型为T的ParameterList。可以编译期迭代或索引ParameterList.get_span()在需要指针线性布局时把元素物化为连续常量数组背后的Span——源码显示它通过#pop.variadic_to_array把param_list展平成扁平数组、再映射为运行期常量并取首元素指针见 variadics.mojo。ParameterList.of、splat等构造器与TypeList的故事一一对应。两个类型刻意保持平行设计文档注释表示未来在 Mojo metatype 故事成熟后有望统一TypeList.map_to_values把每个元素类型经编译期生成器映射为同构值的ParameterList——类型包驱动值包是常见模式。测试 test_variadic.mojo 的test_type_list_map_to_values正是此用法。需要注意的是与参数列表不同参数列表目前不支持异构列表或关键字参数列表文档明确表示若未来有足够需求支撑其复杂度可能加入。Homogeneous variadic arguments运行时同构可变参数上一节讨论的是声明上的参数本节讨论运行时调用点的实参一个参数位接受任意数量的实参但所有实参必须是同一类型T。表面语法同样是前导*但现在它命名的是一个VariadicList而非ParameterList。签名与基本用法同构可变参数形如*args: Int或*parts: String。在 callee 内部args可以像一个小型顺序集合使用len(args)、args[i]、for x in args当T满足Writable时还有args.write_to(s)之类助手来渲染元组形态def print_ints(*values: Int): for i in range(len(values)): print(values[i]) def main(): print_ints(10, 20, 30) # 三个独立实参一个可变参数绑定只关心值不关心索引时迭代器路径同样自然def sum_ints(*values: Int) - Int: var total 0 for v in values: total v return totalcallee 收到的是VariadicList*args: T语法映射到标准库类型VariadicListvariadics.mojo。它的内部字段是var _value: Span[Self._EltPointerType, ImmUntrackedOrigin] # 元素指针的 Span也就是说它携带的是一个指向各实参引用的指针的Span而不是T值的密集数组。这种布局让 callee 能以args[idx]暴露正确的借用或移动语义__getitem__通过self._value.unsafe_ptr()[unsafe_offsetidx][]解引用同时让胶水对象小到足以按值传递还允许非Movable的值通过可变参数传递。文档给出的粗略 lowering 图景仅为示意名非精确 MLIR// Caller: foo(a, b, c) with def foo(*xs: T) 1. 编译器照常为每个实参分配存储栈槽、寄存器或 ABI 要求的形式。 2. 构造一个临时数组其第 i 项是指向第 i 个实参的 pointer-to-ref 所有元素的指针/引用 MLIR 类型相同。 3. 调用 foo传入一个 VariadicList其内部 Span 指向该数组长度为实参个数。 4. VariadicList.__init__由该数组隐式构造把 POP 数组转成元素指针的 Span __getitem__ 通过该 span 索引并加载引用。源码佐证了第 4 步隐式__init__[size, container_origin]接收编译器生成的元素指针数组引用用pop.array.gep取首元素指针后unsafe_bitcast为元素指针类型再包成Span(unsafe_ptr..., lengthsize)见 variadics.mojo。所以可变参数束永远是指针的 span——即使T是 trivial 类型也是如此。阅读性能文档时需记住可变参数在 caller 侧仍是独立对象列表只是一层用于统一索引的间接层。借用与拥有实参默认情况下*args: T借用每个元素。当需要取得所有权时例如T是线性类型或想用consume_elements移出使用var *args: T。VariadicList用is_owned参数跟踪该状态置位时__deinit__反向遍历列表、逐个销毁元素与常规实参析构顺序一致见 variadics.mojo__parameter def destroy_elem(_idx: Int, var arg: ExplicitDelOnly): arg^.destroy() def take_owned_linear(var *args: ExplicitDelOnly): args^.consume_elements[destroy_elem]() # Caller 传临时对象callee 逐个消费。 take_owned_linear(ExplicitDelOnly(5), ExplicitDelOnly(10))该示例直接取自 test_variadic.mojo 的test_variadic_list_linear_type。consume_elements调用处的^选择可变参数束的 owned 视图。consume_elements与想要var元素的 API许多下沉一串值的 API 与List列表字面量构造器形状一致var *values: Self.T加上values^.consume_elements[...]把每个实参移入新分配的存储。从实现看consume_elements(deinit self, elt_handler)只在Self.is_owned时可用where约束即报错信息consume_elements may only be called on owned variadic lists它用__get_address_as_owned_value逐个转移所有权给处理器闭包见 variadics.mojo。值得注意的是源码注释这里刻意不用Pointer.unsafe_take_pointee因为它要求元素是Movable而VariadicList明确不需要这一前提。打印与调试当element_type满足Writable时VariadicList实现write_to/write_repr_to内部_write_elements循环拼接(a, b, c)形态is_reprTrue时逐元素调用write_repr_towrite_repr_to还会套上VariadicList[Int]类型名见 variadics.mojo。这就是为什么测试期望write_to输出(1, 2, 3)、write_repr_to输出VariadicListInt, Int(2), Int(3)))。测试用例见test_variadic_list_write_to与test_variadic_list_write_repr_totest_variadic.mojo。Variadic packs异构可变参数包可变参数包variadic pack是同构VariadicList的异构对应物。不再用*args: T单一静态元素类型而是把类型参数包与实参包配对实参类型取自该包def callee*Ts: Writable raises: ...callee 收到一个VariadicPack定义于 variadics.mojo。与VariadicList一样它是RegisterPassable且参与所有权is_owned、var *args: *Ts、consume_elements、__del__但内部表示是类似Tuple的异构值其底层 MLIR 类型是!lit.ref.pack:param_listTrait ... isParamPack形态的!kgen.struct见 variadics.mojo每个槽位可能对应不同大小、不同 ABI 的具体类型。为什么包需要comptime for每个实参槽位可能是不同的具体类型大小与 ABI 各异。因此包更接近元组而非数组不存在单一的T可用于args[runtime_idx]。索引通过__getitem_param__[index]暴露使用编译期索引实现中用lit.ref.pack.extract抽取槽位编译器才能为每个位置生成正确的加载指令。这正是惯用代码用comptime for而非运行期for i in range(len(args))遍历包的原因def count_many_things*ArgTypes: Intable - Int: var total 0 comptime for i in range(args.__len__()): # 每个 args[i] 都有来自 *ArgTypes 的不同具体类型。 total Int(args[i]) return total def main(): print(count_many_things(Int8(5), UInt32(11), Int(12))) # 28该示例改编自VariadicPack的 docstringvariadics.mojo。关键点循环变量i是编译期参数因此每个args[i]都单独 monomorphize。VariadicPack.__len__直接返回Self.Ts.length类型包长度编译期已知。Writable包与转发当每个元素类型都满足Writable时包实现write_to及相关操作def helper*Ts: Writable raises: var s String() args.write_to(s) # 对 (1, hello, True) - (1, hello, True) def forwarder*Ts: Writable raises: helper(*args) # splat 原样转发包 # Caller: forwarder(1, hello, True)转发模式callee(*args)在test_variadic_pack_forwarding、单元素变体test_variadic_pack_forwarding_single_element、空包变体test_variadic_pack_forwarding_empty以及多跳版本test_variadic_pack_forwarding_through_two_levels中均有覆盖test_variadic.mojo。空包与单元素包的转发方式完全相同对应测试中的forwarder()与forwarder(42)。SomeTypeList语法糖当只需要任意数量的类型且每个都满足Trait时可以不引入显式*Ts绑定直接在def上命名实参包def foo(*args: *SomeTypeList[Writable]) raises: var s String() args.write_to(s)SomeTypeList是定义于 Mojo/stdlib/std/builtin/anytype.mojo 的编译期别名把Some[T: Trait]的思想扩展到受同一 trait 约束的整个TypeList在可变调用点尤其有用。对应测试为test_variadic_pack_sometest_variadic.mojo。与Tuple的关系Tuple是拥有异构序列的典型结构体。其构造函数接收一个与其元素类型列表对齐的可变参数包# 概念示意见 Mojo/stdlib/std/builtin/tuple.mojo struct Tuple*element_types: Movable: def __init__(out self, var *args: *Self.element_types): ...也就是说VariadicPack本质上就是运行期被降级为结构体的异构元组编译器在编译期掌握每个元素的精确类型以生成正确的内存布局与访问代码。Variadic keyword arguments可变关键字参数可变关键字参数是 Python**kwargs的运行时对应物。callee 可以接受任意数量的额外关键字实参且这些实参的值共享同一个类型V。键始终是运行时String调用点写下的关键字名。与可变参数包不同这里没有异构值类型列表**kwargs: Int意味着每个传入值都必须是Int而非混合类型元组。表面语法是参数名前导**且必须位于签名末尾、其他参数之后def variadic_kwargs(a: Int, b: Int, *args: Int, c: Int, d: Int, **kwargs: Int): pass def print_nicely(**kwargs: Int): for item in kwargs.items(): print(item.key, , item.value) def main(): print_nicely(a7, y8)每个函数只允许一个**参数且必须带类型注解例如**kwargs: Int不允许裸写**kwargs。解析器侧的支持可见于 Mojo/test/mojo-parser/decls/variadic_kwargs.mojo 与 Mojo/lib/MojoParser/Signatures.cpp后者负责签名中kw_vararg槽位的解析。callee 收到的是StringDict文档中的OwnedKwargsDict设计文档写作时**kwargs: V以var kwargs: OwnedKwargsDict[V]传入。当前仓库中该类型已更名为StringDict[V]定义于 Mojo/stdlib/std/collections/dict.mojo其 docstring 明确写着用于向函数传递拥有的可变关键字实参的容器且用户不应直接实例化它——编译器在调用点构建、传入 callee。这一更名在 Mojo/docs/site/releases/v1.0.0.md 的发布说明中有记录OwnedKwargsDict→StringDict。StringDict内部包装了一个Dict[String, V, default_comp_time_hasher]对外暴露字典风格接口len、in、__getitem__支持按String或ImmStringSpan查找后者免分配、__setitem__、keys()、values()、items()、find、pop等另有deinit_with用于值非Deinitable时的显式析构。关键字可变参数以拥有var方式传递因为字典通常为单个调用点构建。不能显式写read或mut约定# 暂不支持。 def borrowed_kwargs(mut **kwargs: Int): ...在 callee 内部kwargs拥有该字典及其插入的值这与调用 lowering 用_insert把每个关键字操作数转入字典的方式一致。调用 lowering文档给出的粗略 lowering 图景仅为示意名非精确 MLIR// Caller: foo(x9, stuff8) with def foo(**kwargs: Int) 1. 分配一个空的 OwnedKwargsDict[Int]局部临时对象。 2. 对调用点每个关键字操作数 - 把键物化为编译期 String 字面量。 - 求值值表达式。 - 调用 OwnedKwargsDict::_insert(dict, key, value)把值的所有权转入字典。 3. 把填好的字典作为拥有的 **kwargs 实参传给 foo。重载解析收集所有未绑定到前面具名参数的关键字操作数路由到kw_vararg槽位若 callee 没有**kwargs参数这些操作数即报错。位置参数*args与**kwargs可出现在同一签名中各自吃掉自己种类的操作数。用**kwargs^转发要把整个关键字束转发给另一个可变关键字 callee使用双星解包形式并配合^转移所有权def pass_kwargs(**kwargs: Int): takes_int_variadic_kwargs_multiline(**kwargs^)不加^时转发会尝试拷贝字典并因StringDict不可隐式拷贝而失败加上^后caller 的字典被移入内层调用。这是callee(*args)splat 可变参数包的关键字对应物。当前尚不支持从普通Dict[String, V]解包例如print_nicely(**my_dict)今天只有从另一个**kwargs绑定转发可用。泛型推断当函数在值类型上泛型时关键字实参可以像位置实参一样驱动类型推断def infers_from_kwargsT: SomeTrait: pass # T 由关键字值推断为 MemOnly。 infers_from_kwargs(yMemOnly(), zs)推断出的元素类型必须满足声明的 trait 约束且兼容转入StringDict._insertowned、movable 值。测试 Mojo/stdlib/test/collections/test_dict.mojo 的test_owned_kwargs_dict与第 1944 行的test_owned_kwargs_dict_linear覆盖了可变关键字参数在 callee 内部暴露的字典 API 表面以及线性值非Copyable的转移规则。当前限制以下缺口是首个版本的有意取舍亦可参见 Mojo 手册 functions 章节仅支持同构值所有关键字共享一个值类型V键始终是String没有类型化键变体始终 owned不支持read **kwargs或mut **kwargs调用点不支持从普通Dict解包不支持编译期可变关键字参数声明上的**kwparams: ...值必须是Movable并满足StringDict的约束非拥有的线性值遵循与其他 owned 调用实参相同的转移规则。实践建议与测试参照选择形态只需要同类型的一串值时用*args: TVariadicList需要每个槽位类型不同、类型由 trait 约束时用*args: *TsVariadicPack并务必用comptime for遍历需要收集任意关键字键值对时用**kwargs: VStringDict[V]。所有权要消费移动实参一律写成var *args: T/var *args: *Ts再经consume_elements或**kwargs^转移借用场景保持默认read语义。元编程TypeList的map/reduce/filter_idx/contains与ParameterList.get_span()是编译期类型/值计算的主力工具SomeTypeList[Trait]是可变调用点上约束类型包的最简写法。验证语法任何语法细节以测试文件 Mojo/stdlib/test/builtin/test_variadic.mojo 为准其中test_variadic_*、test_type_list_*、test_parameter_list_*、test_variadic_pack_*、test_dynamic_variadic_pack等用例分别覆盖了本文讨论的各条路径关键字字典相关验证在 Mojo/stdlib/test/collections/test_dict.mojo。文档仍标注为 DraftTypeList/ParameterList的统一、**kwparams编译期参数等能力属于未来演进方向实际能力请以当前仓库代码与测试为准。【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考