Mojo 最近引入了特征,所以我想尝试一下。目前,内置特征包括 、 、 、 、 、 和 (看起来“-able”后缀是这些命名约定中的一个东西!)。 特征适用于结构。要实现特征,您只需将一个方法添加到与该特征匹配的结构中即可;然后将特征名称作为参数传递: CollectionElement Copyable Destructable Intable Movable Sized Stringable @value struct Duck(Quackable): fn quack(self): print("Quack!") Mojo 中的 装饰器将 、 和 等生命周期方法插入到结构中,稍微简化了我们的生活,因为我们不必自己添加它们。 @value __init__() __copyinit__() __moveinit__() Mojo 中的 Traits 尚不支持方法的默认实现,因此,上面的 方法主体中的 。您还可以使用 ,这将具有与 Python 中相同的效果。 Quackable ... pass Mojo 特性与 Go 接口 尽管名称不同,Mojo 中特征的基本方法让我想起了 Go 接口。在 Go 中,您可以定义一个 结构并实现一个 接口,如下所示: Duck Quackable type Quackable interface { quack() } 并创建一个满足此接口的结构: type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") } 将其与 Mojo 实现进行比较: trait Quackable: fn quack(self): ... @value struct Duck(Quackable): fn quack(self): print("Quack!") 我认为 Mojo 版本更加简洁,将 方法定义嵌套在 结构类型中。除了处理结构之外,Mojo 中的特征不需要 关键字,就像 Go 中一样。 quack() Duck implements 有趣的是,Mojo 文档中指出, 不允许默认实现,这意味着将来可能会允许它们。这意味着 Mojo 可能会追求一种与 Go 不同的方法,Go 侧重于 接口的结构,而不是 它。 目前还 满足 实现 Go 中不需要默认方法实现,并且通过嵌入可以实现类似的效果,因为接口/特征实现只是 Go 中不存在的概念。 Mojo 的情况可能有所不同。 如何使用 Mojo 特征 特征和接口的价值在于使代码可重用。例如,在 Mojo 中,您可以编写接受特征类型的函数…… fn make_it_quack[T: Quackable](could_be_duck: T): could_be_duck.quack() 然后传递实现相同特征的不同结构作为输入 - 一切都会正常工作!例如,下面是一个实现 结构: Quackable Goose @value struct Goose(Quackable): fn quack(self): print("Honk!") 在这里,要在 和 上调用 (请记住,您需要 Mojo 中的 函数作为程序的入口点): Goose Duck make_it_quack main def main(): make_it_quack(Duck()) make_it_quack(Goose()) 其输出将是 Quack! Honk! 特质错误 如果我尝试将未实现 特征的内容传递给 函数,例如 ,则程序将无法编译: Quackable make_it_quack StealthCow @value struct StealthCow(): pass make_it_quack(StealthCow()) 下面的错误消息可能更具描述性;也许Mojo团队会改进它? error: invalid call to 'make_it_quack': callee expects 1 input parameter, but 0 were specified 如果我从 中删除 方法,情况也是如此;在这里,我们得到一个很好的描述性错误: Goose quack struct 'Goose' does not implement all requirements for 'Quackable' required function 'quack' is not implemented 与纯 Python 方法相比,这种静态类型检查的优点是我们可以轻松捕获错误并避免将任何错误传送到生产环境,因为代码根本无法编译。 遗产 Mojo 中的特征已经支持继承,因此我们的“Quackable”特征可以扩展“Audible”特征,如下所示: trait Audible: fn make_sound(self): ... trait Quackable(Audible): fn quack(self): ... 这意味着 结构必须实现 和 才能符合 特征。 Duck quack make_sound Quackable 这类似于 Go 中“接口嵌入”的概念,要创建一个继承其他接口的新接口,您可以像这样嵌入父接口: type Quackable interface { Audible // includes methods of Audible in Quackable's method set } 静态方法 Traits 还接受无需创建结构体实例即可工作的静态方法: trait Swims: @staticmethod fn swim(): ... @value struct Duck(Quackable, Swims): fn quack(self): print("Quack!") @staticmethod fn swim(): print("Swimming") fn make_it_swim[T: Swims](): T.swim() 您可以像这样调用静态方法: def main(): make_it_quack(Duck()) make_it_quack(Goose()) Duck.swim() 这将输出: Quack! Honk! Swimming 请注意,在最后一个方法调用中,未创建 Duck 实例。这就是 Python 中静态方法的工作方式,与面向对象编程有些不同。 Mojo 建立在这个 Python 功能之上。 Mojo Traits 与 Go 接口相比的局限性 有趣的是,带有空 的 Go 技巧允许传递任何类型,并且在 Go 泛型引入之前在 Go 社区中很流行,但它不适用于 Mojo 类型的函数 。 interface{} fn 您的结构必须至少实现一种生命周期方法,例如 或 ,在本例中,这将使其符合 或 ,以便与接受特征类型的函数一起使用。 __len__ __str__ Sized Stringable 这实际上并不是一个真正的限制,并且在 Mojo 中很有意义,因为使用 Mojo,如果您需要动态类型,您可以退回到更灵活的 函数,然后应用通常的 Python 魔法来处理未知类型。 def 更严格的 Mojo 函数也适用于使用 等类型的通用结构; 阅读更多相关内容。 fn DynamicVector 在这里 我看到的另一个限制与结构及其方法而不是特征有关,并且是实现清洁架构/将代码分成不同部分的潜在障碍。 考虑前面使用 Go 与 Mojo 结构方法定义的示例之一: type Duck struct {} func (d Duck) quack() { fmt.Println("Quack!") } @value struct Duck(Quackable): fn quack(self): print("Quack!") Mojo 示例遵循更像 Python 的语法,将方法定义直接嵌套在结构内部,而 Go 版本允许将方法定义与结构本身分开。在这个版本中,如果我有一个包含许多类型和方法的很长的结构,那么阅读起来会有些困难。 但这并不是一个关键的区别,只是需要注意的事情。 尽管 Mojo 语言还处于早期阶段,但 Mojo 特征已经非常有用了。虽然 Go 哲学强调简单性并试图将功能保持在最低限度,但未来我们很可能会看到 Mojo 特征中添加更多功能,使它们更加强大并支持大量不同的用例。