Skip to content

Lua 模块

在 Lua 中,模块本质上是一个库(library),它提供了一系列函数和变量,通常封装在一个表中。这个表充当一个命名空间(namespace),防止与代码的其他部分或其他模块发生命名冲突。模块使用 require 函数加载,该函数通常返回这个表,允许你访问其内容。

使用表作为模块提供了显著的灵活性,允许像操作其他任何 Lua 表一样操作模块。这种动态特性意味着 Lua 不需要特殊的语言结构来实现其他语言可能需要的功能。你可以通过多种方式与模块函数交互:

-- 假设我们有一个名为 'printFormatter.lua' 的模块
-- 并且 printFormatter.lua 包含一个函数 simpleFormat(arg)
-- Method 1: Direct access after require
-- 方法 1:require 后直接访问
require "printFormatter"
printFormatter.simpleFormat("test")
-- Method 2: Assign the returned module table to a local variable (recommended)
-- 方法 2:将返回的模块表赋值给局部变量(推荐)
local formatter = require "printFormatter"
formatter.simpleFormat("test")
-- Method 3: Get a direct reference to a function from the module
-- 方法 3:从模块获取函数的直接引用
local formatterFunction = require("printFormatter").simpleFormat
formatterFunction("test")
-- Method 4: If the module is already loaded and assigned
-- 方法 4:如果模块已加载并赋值
local myFormatter = require "printFormatter"
local sf = myFormatter.simpleFormat -- Local alias for brevity
-- 局部别名,用于简化
sf("another test")

这些示例展示了 Lua 在模块使用方面的灵活方法,无需复杂的语法。

Lua 提供了 require 函数来加载模块。当你调用 require("modulename") 时,Lua 会在预定义的路径序列中搜索该模块。对于 Lua 文件,它检查 package.path;对于 C 库,它检查 package.cpath。require 函数是智能的:它只加载模块一次。后续对同一模块的 require 调用将返回第一次调用缓存的值。模块应是一个代码块(chunk of code),它定义了一些值(通常是函数或包含函数的表),并且最重要的是,返回一个单一值(通常是模块表本身)。

让我们创建一个名为 mymath.lua 的简单数学模块。模块中的函数最好返回值而不是直接打印,这样可以使模块更具可重用性。

-- File: mymath.lua
-- 文件:mymath.lua
local mymath = {}
-- It's good practice to make module functions local if they are helpers,
-- 将模块函数设为 local 是个好习惯,如果它们是助手函数
-- or attach them directly to the module table if they are public.
-- 或者如果它们是公共接口,则直接附加到模块表。
function mymath.add(a, b)
return a + b
end
function mymath.subtract(a, b)
return a - b
end
function mymath.multiply(a, b)
return a * b
end
function mymath.divide(a, b)
if b == 0 then
return nil, "Error: Division by zero"
end
return a / b
end
return mymath

要在另一个 Lua 文件中使用此模块,比如 main.lua(确保这两个文件位于同一目录中,或者 mymath.lua 在 Lua 的包路径中):

-- File: main.lua
-- 文件:main.lua
local mymathmodule = require("mymath")
print("Addition (10, 20):", mymathmodule.add(10, 20)) -- Expected: 30
-- 加法 (10, 20): 期望值:30
print("Subtraction (30, 20):", mymathmodule.subtract(30, 20)) -- Expected: 10
-- 减法 (30, 20): 期望值:10
print("Multiplication (10, 20):", mymathmodule.multiply(10, 20))-- Expected: 200
-- 乘法 (10, 20): 期望值:200
local result, err = mymathmodule.divide(30, 20)
if err then
print(err)
else
print("Division (30, 20):", result) -- Expected: 1.5
-- 除法 (30, 20): 期望值:1.5
end
local result2, err2 = mymathmodule.divide(5, 0)
if err2 then
print(err2) -- Expected: Error: Division by zero
-- 期望值:错误:除数为零
else
print("Division (5,0):", result2)
end

当你运行 lua main.lua 时,输出将是:

Addition (10, 20): 30
Subtraction (30, 20): 10
Multiplication (10, 20): 200
Division (30, 20): 1.5
Error: Division by zero
  • 将模块文件放在 Lua 可以找到它们的位置(与主脚本在同一目录,或位于 package.path 中列出的目录)。
  • 在 require("modulename") 中使用的模块名应与文件名对应(例如 modulename.lua)。
  • 标准且推荐的做法是模块返回一个包含其公共接口的表。
  • 如果模块函数和变量不属于公共接口,则将其声明为 local,以促进封装(encapsulation)。

过时的模块实现(module() 函数)

Section titled “过时的模块实现(module() 函数)”

在 Lua 的旧版本(主要是 Lua 5.1)中,模块有时使用 module("name", package.seeall) 函数定义。这种方法现已废弃且强烈不推荐。它会污染全局命名空间(global namespace)并带来其他不良副作用。

-- Old, deprecated way (mymath_old.lua)
-- 旧的、已废弃的方式 (mymath_old.lua)
module("mymath_old", package.seeall)
function add(a, b) -- Note: this becomes a global function!
-- 注意:这会成为一个全局函数!
print(a + b)
end
function sub(a, b)
print(a - b)
end
-- ... and so on for mul and div
-- ... mul 和 div 类似

使用这种旧式模块:

-- main_old.lua
require("mymath_old")
-- Functions from mymath_old are now global or accessed via the global table mymath_old
-- 来自 mymath_old 的函数现在是全局的,或者通过全局表 mymath_old 访问
-- This depends on the exact behavior of the `module` function version used.
-- 这取决于使用的 `module` 函数版本的具体行为。
-- For example, you might call add(10, 20) directly.
-- 例如,你可以直接调用 add(10, 20)。
mymath_old.add(10,20)
mymath_old.sub(30,20)

虽然你可能在遗留代码中遇到这种模式,但新项目应始终使用返回表的现代方法。module() 函数已在 Lua 5.2 中移除。这种旧式风格安全性较低,管理更困难,并且与当前模块化设计的最佳实践相悖。