0Pricing
Lua Academy · 课时

编写模块文件

通过从 .lua 文件返回函数表来创建模块。

编写模块文件 是 CoddyKit 上的免费 Lua Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Lua Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Lua Academy 课程共包含 4 节课。

基本模块模式

Lua 的标准模块模式是:创建一个局部表 M,向其中添加函数和值,最后返回它。这个表就是模块的公共 API。文件中的其他局部变量都是私有的。

-- stringutils.lua
local M = {}

function M.trim(s)
  return s:match("^%s*(.-)%s*$")
end

function M.split(s, sep)
  local t = {}
  for p in s:gmatch("[^"..sep.."]+") do t[#t+1]=p end
  return t
end

return M

-- Usage:
-- local su = require("stringutils")
-- print(su.trim("  hello  "))

私有状态

在模块文件中使用 local 声明的变量是私有的——调用者无法访问它们。模块中的函数可以通过上值访问私有状态。这是 Lua 的封装机制。

-- counter.lua
local M = {}
local count = 0   -- private state

function M.increment(n)
  count = count + (n or 1)
end

function M.reset()
  count = 0
end

function M.get()
  return count
end

return M

带初始化的模块

有些模块需要初始化(例如配置和连接)。您可以将初始化代码放在模块级别(文件顶部),也可以放在显式的 M.init() 函数中。模块级别的方式会在第一次 require 时运行一次;init() 则需要显式调用。

-- cache.lua
local M = {}
local store = {}   -- initialized at load time
local hits = 0
local misses = 0

function M.get(key)
  if store[key] ~= nil then
    hits = hits + 1
    return store[key]
  end
  misses = misses + 1
  return nil
end

function M.set(key, val) store[key] = val end
function M.stats() return {hits=hits, misses=misses} end

return M

带类的模块

模块可以导出一个类:也就是包含构造函数的表。返回的模块表包含 new() 函数,也可以同时充当类的元表。

-- point.lua
local Point = {}
Point.__index = Point

function Point.new(x, y)
  return setmetatable({x=x, y=y}, Point)
end

function Point:distance(other)
  local dx, dy = self.x-other.x, self.y-other.y
  return math.sqrt(dx*dx + dy*dy)
end

function Point:__tostring()
  return string.format("(%g,%g)", self.x, self.y)
end

return Point

-- Usage:
-- local Point = require("point")
-- local p = Point.new(3, 4)

模块常量

将常量添加到模块表中即可导出它们。按照惯例,常量使用大写形式。由于 Lua 没有 const,用户在技术上仍然可以修改它们,但大写命名表示“不要修改”。

-- colors.lua
local M = {}

M.RED   = {r=255, g=0,   b=0}
M.GREEN = {r=0,   g=255, b=0}
M.BLUE  = {r=0,   g=0,   b=255}
M.WHITE = {r=255, g=255, b=255}
M.BLACK = {r=0,   g=0,   b=0}

function M.toHex(c)
  return string.format("#%02X%02X%02X", c.r, c.g, c.b)
end

return M

模块版本控制

在模块中包含版本字段。调用者可以检查版本,以确保兼容性。请使用语义化版本号(major.minor.patch)。

-- mylib.lua
local M = {}
M._VERSION = "1.2.3"
M._NAME = "mylib"
M._DESCRIPTION = "My Lua library"

function M.hello(name)
  return "Hello, " .. (name or "World") .. "!"
end

return M

-- Usage:
local mylib = require("mylib")
print(mylib._VERSION)   -- 1.2.3
print(mylib.hello("Lua"))

子模块

大型库通常会拆分为多个子模块。主模块可以 require 并重新导出子模块,也可以让每个子模块独立使用。请将文件组织在与模块路径相匹配的目录中。

-- mylib/init.lua  (loaded by require("mylib"))
local M = {}

M.strings = require("mylib.strings")
M.tables  = require("mylib.tables")
M.math    = require("mylib.math")

M._VERSION = "2.0.0"

return M

-- Users can require the whole library:
-- local mylib = require("mylib")
-- mylib.strings.trim(...)

-- Or individual sub-modules:
-- local strs = require("mylib.strings")

带元表的模块

为模块设置包含 __call 的元表,即可使模块可调用。这适用于主要功能是函数、同时还带有实用工具的模块——“主要”操作是调用,实用工具则是字段。

-- format.lua
local M = {}
setmetatable(M, {__call = function(_, fmt, ...)
  return string.format(fmt, ...)
end})

function M.pad(s, width, char)
  char = char or " "
  return string.rep(char, math.max(0, width - #s)) .. s
end

return M

-- Usage:
-- local fmt = require("format")
-- print(fmt("%.2f", 3.14))   -- 3.14
-- print(fmt.pad("42", 5))    --    42

延迟加载子模块

使用 __index 延迟加载子模块——仅在首次访问时加载。对于包含许多子模块的大型库,这可以加快启动速度。

-- biglib.lua
local M = {}
local submodules = {"strings", "tables", "math", "io"}

setmetatable(M, {
  __index = function(t, k)
    for _, name in ipairs(submodules) do
      if name == k then
        local mod = require("biglib." .. k)
        rawset(t, k, mod)
        return mod
      end
    end
    return nil
  end
})

return M

-- Loads biglib.strings only when accessed:
-- local lib = require("biglib")
-- lib.strings.trim(...)

模块测试模式

在模块文件底部添加测试函数或代码块,并设置条件,使其仅在文件被直接运行时执行(而不是被 require 时执行)。这样可以让单元测试与代码放在一起。

-- utils.lua
local M = {}

function M.clamp(v, lo, hi)
  return math.max(lo, math.min(hi, v))
end

-- Self-test: only runs when executed directly
if debug.getinfo(2, "S") == nil then
  -- Running as main script, not required
  print("Testing clamp...")
  assert(M.clamp(5, 0, 10) == 5)
  assert(M.clamp(-1, 0, 10) == 0)
  assert(M.clamp(15, 0, 10) == 10)
  print("All tests passed!")
end

return M

快速检查

在 Lua 模块中定义私有状态的标准方式是什么?

回顾:编写模块

总结:

  • 模式:local M = {} ... return M
  • 文件中的局部变量 = 私有;M 字段 = 公共 API
  • 模块级代码会在第一次 require 时运行一次
  • 通过模块表中的构造函数导出类
  • 通过 __index 延迟加载子模块
  • 包含 _VERSION 以检查兼容性

常见问题解答

「编写模块文件」课时是免费的吗?

是的 — 「编写模块文件」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Lua Academy 课程的其余内容,请升级到 CoddyKit PRO。 Lua Academy 课程共包含 4 节课。

「编写模块文件」这节课中我会学到什么?

通过从 .lua 文件返回函数表来创建模块。 你通过在浏览器中直接运行的动手代码来练习 Lua Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Lua Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Lua Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「编写模块文件」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Lua Academy 课中编写并运行代码吗?

能。每节 Lua Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. require 函数
  2. 编写模块文件
  3. package.path 和 package.cpath
  4. 模块模式和最佳实践
← 返回 Lua Academy