模块与包管理

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 的工作原理

  1. package.loaded 表中查找模块是否已加载
  2. 如果已加载,直接返回缓存的值
  3. 如果未加载,根据 package.searchers 查找模块
  4. 找到后执行模块文件,将返回值存入 package.loaded
  5. 返回模块值
1
2
3
4
5
6
7
8
9
10
11
12
13
-- 验证 require 的缓存机制
local m1 = require("math_utils")
local m2 = require("math_utils")
print(m1 == m2) --> true(同一个实例)

-- 查看已加载的模块
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)
-- 输出类似:./?.lua;/usr/local/share/lua/5.4/?.lua;...

-- ? 会被模块名替换
-- 如 require("math_utils") 会搜索:
-- ./math_utils.lua
-- /usr/local/share/lua/5.4/math_utils.lua
-- ...

-- 自定义搜索路径
package.path = package.path .. ";/my/modules/?.lua"

package.searchers

package.searchers 是一个数组,定义了模块的查找方式:

1
2
3
4
5
6
7
8
-- 查看 searchers
for i, searcher in ipairs(package.searchers) do
print(i, searcher)
end
-- 通常包含:
-- 1. lua searcher(查找 .lua 文件)
-- 2. C searcher(查找 .dll/.so 文件)
-- 3. 自定义 searcher...

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
-- math_utils.lua
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
-- counter.lua
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()) --> 12

方式三:直接返回值

1
2
3
4
5
6
7
-- config.lua
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.lua

1
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)) --> 120
print(str_mod.capitalize("hello")) --> Hello

modules/math.lua

1
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.lua

1
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
-- require("utils.math") 会搜索 utils/math.lua
-- require("utils.math.advanced") 会搜索 utils/math/advanced.lua

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
-- utils/init.lua
local utils = {}

utils.math = require("utils.math")
utils.string = require("utils.string")

function utils.version()
return "1.0"
end

return utils

-- main.lua 中
local utils = require("utils")
print(utils.version()) --> 1.0
print(utils.math.factorial(5)) --> 120

13.6 module() 函数(Lua 5.1 遗留)

Lua 5.1 提供了 module() 函数,但在 5.2+ 中已经移除。了解即可,不要在新代码中使用

1
2
3
4
5
6
7
8
9
10
11
-- Lua 5.1 风格(已废弃)
module("mymod", package.seeall)

function hello()
print("Hello from mymod")
end

-- 这种方式的缺点:
-- 1. 自动创建全局变量(污染命名空间)
-- 2. 强制使用 package.seeall 会暴露全局环境
-- 3. 在 Lua 5.2+ 中已移除

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)) --> 25

13.8 LuaRocks 包管理器

LuaRocks 是 Lua 的包管理器,类似 Python 的 pip 或 Node.js 的 npm。

安装 LuaRocks

1
2
3
4
5
6
7
8
# Windows
winget install LuaRocks

# macOS
brew install luarocks

# Linux
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
-- 确保 LuaRocks 路径在 package.path 中
-- 通常安装后会自动配置

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
-- my-module-1.0-1.rockspec
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 的包管理器
  • 模块化是大型项目的基础

练习题

  1. 将第 12 章的 OOP 类工厂提取为独立的 class.lua 模块
  2. 创建一个 logger.lua 模块,支持不同日志级别(debug / info / warn / error)
  3. 实现一个配置模块 config.lua,支持从文件读取和合并默认配置
  4. 创建一个 validator.lua 模块,提供各种输入验证函数(邮箱、手机号、URL)
  5. 使用 LuaRocks 安装 penlight,探索其提供的拓展函数
ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
Lua基础教程 系列
第 13/15 篇
分享: