Lua 模块
当程序变大后,你不可能把所有代码都写在一个文件里。模块用于把相关函数和数据放到独立文件中,再通过 require() 加载。
你可以把模块理解成一个返回 table 的 Lua 文件。
第一个模块
创建文件 mymath.lua:
local mymath = {}
function mymath.add(a, b)
return a + b
end
function mymath.sub(a, b)
return a - b
end
return mymath
再创建 main.lua:
local mymath = require("mymath")
print(mymath.add(10, 5))
print(mymath.sub(10, 5))
把两个文件放在同一目录,运行:
lua main.lua
输出:
15
5
这就是现代 Lua 模块的基本写法:模块文件最后 return 一个 table。
require 做了什么
require("mymath") 会寻找并加载名为 mymath 的模块。对于纯 Lua 模块,它通常会查找类似下面的文件:
mymath.luamymath/init.lua
找到后,Lua 会执行这个文件,并把文件的返回值作为 require() 的结果。
local mymath = require("mymath")
如果同一个模块已经加载过,require() 通常会直接返回缓存结果,不会重复执行模块文件。
模块名和文件路径
如果模块在子目录中,例如:
utils/stringx.lua
通常这样加载:
local stringx = require("utils.stringx")
模块名中的点号 . 会映射到路径分隔符。你不需要写 .lua 后缀。
模块中的私有函数
模块文件中的 local 变量和函数不会暴露给外部:
local formatter = {}
local function trim_prefix(text)
return string.gsub(text, "^%s+", "")
end
function formatter.normalize(text)
return string.lower(trim_prefix(text))
end
return formatter
外部只能访问返回 table 里的字段:
local formatter = require("formatter")
print(formatter.normalize(" LUA"))
输出:
lua
trim_prefix() 是模块内部实现细节,不会被外部直接调用。
package.path
Lua 使用 package.path 查找 Lua 模块:
print(package.path)
输出会是一长串搜索模板,例如:
./?.lua;./?/init.lua;...
其中 ? 会被模块名替换。比如 require("utils.stringx") 会尝试查找类似 utils/stringx.lua 的文件。
你可以临时修改 package.path:
package.path = package.path .. ";./libs/?.lua"
这样 Lua 会额外到 libs/ 目录中查找模块。
C 模块和 package.cpath
如果模块是 C 语言编译出来的动态库,Lua 会使用 package.cpath 查找。常见后缀可能是 .so、.dll 或 .dylib。
初学阶段你不需要深入掌握 package.cpath,只要知道 require() 不只能加载 .lua 文件,也能加载 C 扩展模块。
不推荐旧式 module 写法
你可能在旧代码里看到:
module("mymath", package.seeall)
这是 Lua 5.1 时代常见的旧式写法。现代 Lua 教程不建议用它作为主线,因为它会修改模块环境,容易污染命名空间,也不利于理解模块依赖。
推荐写法仍然是:
local M = {}
function M.some_function()
-- ...
end
return M
常见错误
忘记 return 模块 table
如果模块文件没有 return,外部 require() 得到的结果可能不是你期望的 table。
-- bad_module.lua
local M = {}
function M.hello()
print("hello")
end
-- 忘记 return M
正确写法:
return M
文件名和模块名不一致
如果文件叫 my_math.lua,你应该这样加载:
local my_math = require("my_math")
不是:
local my_math = require("mymath")
除非你额外配置了搜索路径或加载器。
小结
你需要掌握:
- 模块通常是一个返回 table 的 Lua 文件。
- 使用
require("module_name")加载模块。 - 模块内部实现细节应尽量使用
local。 - 子目录模块通常用点号表示,例如
require("utils.stringx")。 - 旧式
module(..., package.seeall)不建议作为现代写法。