一个模块文件

-- mymath.lua
local M = {}

function M.add(a, b) return a + b end
function M.sub(a, b) return a - b end

local PI = 3.14159         -- 模块私有(不导出)

return M

要点:

  1. 顶层 local M = {}
  2. 把要导出的挂 M.xxx
  3. 末尾 return M

用它

-- main.lua
local math2 = require("mymath")
print(math2.add(1, 2))      -- 3

-- 别名
local M = require("mymath")
print(M.sub(5, 3))           -- 2

require 返回模块导出的表。

require 的查找规则

Lua 按 package.path 模式查找:

print(package.path)
-- "./?.lua;/usr/local/share/lua/5.4/?.lua;..."

? 是模块名占位符。require("foo.bar") → 尝试 ./foo/bar.lua/usr/local/share/lua/5.4/foo/bar.lua...

第一个找到的胜出。

加 / 改 package.path

-- 把当前目录的 mylib/ 加进搜索路径
package.path = "./mylib/?.lua;" .. package.path

或环境变量 LUA_PATH(构建系统常用)。

require 只执行一次

local a = require("foo")
local b = require("foo")
print(a == b)        -- true(同一个表)

模块执行结果缓存在 package.loaded

-- 强制重载(开发用)
package.loaded["foo"] = nil
local foo = require("foo")

子目录 / 嵌套

project/
├── main.lua
└── lib/
    ├── parser.lua
    └── util/
        └── math.lua
-- 需要先把 lib/ 加到 package.path
package.path = "./lib/?.lua;./lib/?/init.lua;" .. package.path

local parser = require("parser")
local m = require("util.math")    -- 点号 = 路径分隔

util.math → 查找 ./lib/util/math.lua./lib/util/math/init.lua

模块里的 require

-- mylib.lua
local utils = require("utils")     -- 加载兄弟模块
local M = {}
function M.process(x) return utils.clean(x) end
return M

完全和 main 里一样写。

C 模块(动态库)

require("foo") 也会查 package.cpath

print(package.cpath)
-- "./?.so;/usr/local/lib/lua/5.4/?.so;..."

C 写的 Lua 模块编译成 .so/.dllrequire 加载共享库 + 调入口函数 luaopen_foo

例:luarocks install lpeg 装一个 C 模块,然后 require("lpeg") 直接用。

详见 第 24 篇嵌入 C

单文件库的发布模式

-- inspect.lua(来自 https://github.com/kikito/inspect.lua)
local inspect = {}

-- ... 几百行实现 ...

return inspect

下载一个文件,扔项目里 require 即可——很多 Lua 库这样发布。

避免循环依赖

-- a.lua
local b = require("b")
local M = {x = b.y}
return M

-- b.lua
local a = require("a")  -- 循环!
local M = {y = "hi"}
return M

Lua 检测到循环时会返回部分初始化的表——通常导致 nil 错误。

解决:重构——拆出共同依赖到第三个模块;或将引用延后到函数体内:

-- a.lua
local M = {}
function M.process(x)
    local b = require("b")    -- 延迟加载
    return b.f(x)
end
return M

模块约定

  • 文件名 = 模块名
  • 一律返回 table(除非有特殊原因返回函数)
  • 不污染全局(不写 function foo() 没 local)
  • 文件顶部声明所有 require

→ 下一篇 错误处理