13.1 为什么需要模块 当程序变大时,把代码放到一个文件里会导致:
命名冲突 :不同开发者定义的全局变量可能重名
难以维护 :上千行的文件难以阅读和调试
复用困难 :想在其他项目中使用某些功能需要复制粘贴
模块 解决这些问题:
模块有自己的作用域,不污染全局空间
按功能拆分成独立的文件
通过 require 按需加载
13.2 require 机制 require 是 Lua 加载模块的标准方法:
1 2 3 4 5 local math_utils = require ("math_utils" )local sum = math_utils.sum(1 , 2 , 3 )
require 的工作原理
在 package.loaded 表中查找模块是否已加载
如果已加载,直接返回缓存的值
如果未加载,根据 package.searchers 查找模块
找到后执行模块文件,将返回值存入 package.loaded
返回模块值
1 2 3 4 5 6 7 8 9 10 11 12 13 local m1 = require ("math_utils" )local m2 = require ("math_utils" )print (m1 == m2) for name, mod in pairs (package .loaded ) do print (name, mod ) end package .loaded ["math_utils" ] = nil local m3 = require ("math_utils" )
模块搜索路径 package.path 控制了 .lua 文件的搜索路径:
1 2 3 4 5 6 7 8 9 10 11 12 print (package .path )package .path = package .path .. ";/my/modules/?.lua"
package.searcherspackage.searchers 是一个数组,定义了模块的查找方式:
1 2 3 4 5 6 7 8 for i, searcher in ipairs (package .searchers) do print (i, searcher) end
13.3 编写 Lua 模块 方式一:返回 table(推荐) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 local math_utils = {}function math_utils.sum (...) local total = 0 for i = 1 , select ("#" , ...) do total = total + select (i, ...) end return total end function math_utils.average (...) local n = select ("#" , ...) if n == 0 then return 0 end return math_utils.sum(...) / n end function math_utils.factorial (n) if n <= 1 then return 1 end return n * math_utils.factorial(n - 1 ) end local function helper (x) return x * 2 end return math_utils
方式二:返回函数 适合单一功能的模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 local function create_counter (start, step) start = start or 0 step = step or 1 local count = start return { next = function () count = count + step return count end , reset = function () count = start end , value = function () return count end } end return create_counter
使用:
1 2 3 local create_counter = require ("counter" )local c = create_counter(10 , 2 )print (c.next ())
方式三:直接返回值 1 2 3 4 5 6 7 return { version = "1.0.0" , author = "ByteFisher" , debug = false , max_retry = 3 }
13.4 模块使用示例 目录结构 1 2 3 4 5 6 project/ ├── main.lua └── modules/ ├── init.lua ├── math.lua └── string_utils.lua
main.lua1 2 3 4 5 6 7 8 9 10 package .path = package .path .. ";./modules/?.lua" local math_mod = require ("math" )local str_mod = require ("string_utils" )print (math_mod.factorial(5 )) print (str_mod.capitalize("hello" ))
modules/math.lua1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 local M = {}function M.factorial (n) if n <= 1 then return 1 end return n * M.factorial(n - 1 ) end function M.fibonacci (n) if n <= 2 then return 1 end return M.fibonacci(n - 1 ) + M.fibonacci(n - 2 ) end function M.gcd (a, b) if b == 0 then return a end return M.gcd(b, a % b) end return M
modules/string_utils.lua1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local M = {}function M.capitalize (s) return (s:gsub ("^%l" , string .upper )) end function M.camel_to_snake (s) return (s:gsub ("%u" , "_%1" ):lower ():gsub ("^_" , "" )) end function M.split (s, sep) sep = sep or "," local result = {} for part in s:gmatch ("[^" .. sep .. "]+" ) do table .insert (result, part) end return result end return M
13.5 子模块与路径 模块名中的点 点号 . 对应目录层级:
1 2 3 4 local advanced_math = require ("utils.math.advanced" )
init.lua 模块当目录下包含 init.lua 时,目录名本身也可作为模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 local utils = {}utils.math = require ("utils.math" ) utils.string = require ("utils.string" ) function utils.version () return "1.0" end return utilslocal utils = require ("utils" )print (utils.version()) print (utils.math .factorial(5 ))
13.6 module() 函数(Lua 5.1 遗留) Lua 5.1 提供了 module() 函数,但在 5.2+ 中已经移除。了解即可,不要在新代码中使用 :
1 2 3 4 5 6 7 8 9 10 11 module ("mymod" , package .seeall )function hello () print ("Hello from mymod" ) end
13.7 package.preload 可以预加载模块而不直接执行文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package .preload ["quick_math" ] = function () local M = {} function M.square (x) return x * x end function M.cube (x) return x * x * x end return M end local qm = require ("quick_math" )print (qm.square(5 ))
13.8 LuaRocks 包管理器 LuaRocks 是 Lua 的包管理器,类似 Python 的 pip 或 Node.js 的 npm。
安装 LuaRocks 1 2 3 4 5 6 7 8 winget install LuaRocks brew install luarocks sudo apt install luarocks
常用命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 luarocks search luaunit luarocks install luaunit luarocks remove luaunit luarocks list luarocks show luaunit
推荐 LuaRocks 包
包名
用途
luafilesystem
文件系统操作
luaunit
单元测试框架
penlight
实用工具库(扩展 Lua)
lua-cjson
JSON 编解码
luasocket
网络编程
lpeg
解析表达式文法
在项目中使用 LuaRocks 1 2 3 4 5 6 7 8 9 local json = require ("cjson" )local lfs = require ("lfs" )local data = {name = "Lua" , version = 5.4 }print (json.encode(data))
13.9 编写可发布模块的规范 模块结构 1 2 3 4 5 6 7 8 9 my-module/ ├── README.md ├── my-module-1.0-1.rockspec ├── src/ │ └── mymodule/ │ ├── init.lua │ └── utils.lua └── test/ └── test_mymodule.lua
rockspec 文件示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package = "my-module" version = "1.0-1" source = { url = "https://github.com/user/my-module/archive/v1.0.tar.gz" } description = { summary = "My awesome Lua module" , detailed = [[A detailed description]] , homepage = "https://github.com/user/my-module" , license = "MIT" } dependencies = { "lua >= 5.3" } build = { type = "builtin" , modules = { ["mymodule" ] = "src/mymodule/init.lua" , ["mymodule.utils" ] = "src/mymodule/utils.lua" } }
13.10 本章小结
require 是模块加载标准方式,具有缓存机制
模块用 return table 是最佳实践
package.path 控制 .lua 文件搜索路径
点号 . 表示子模块路径
package.loaded 缓存已加载模块
package.preload 预注册模块
LuaRocks 是 Lua 的包管理器
模块化是大型项目的基础
练习题
将第 12 章的 OOP 类工厂提取为独立的 class.lua 模块
创建一个 logger.lua 模块,支持不同日志级别(debug / info / warn / error)
实现一个配置模块 config.lua,支持从文件读取和合并默认配置
创建一个 validator.lua 模块,提供各种输入验证函数(邮箱、手机号、URL)
使用 LuaRocks 安装 penlight,探索其提供的拓展函数