在用户的观点来看,一个模块就是一个程序库,可以通过require
来加载,然后便得到一个全局变量,表示一个table
。这个table
就像是一个名称空间,其内容就是模块中导出的所有东西。
require(使用模块)
, module(创建模块)
-- 最简单的使用方法
require "mod"
mod.foo()
-- 可以为模块设置一个局部名称
local m = require "mod"
m.foo()
m.foo()
对于require而言,一个模块就是一段定义了一些值的代码。
function require(name)
if not package.loaded(name) then -- 模块是否已加载?
local loader = findloader(name)
if loader == nil then
error("unable to load module" .. name)
end
package.laoded[name] = true -- 模块标记为已加载
local res = loader(name) -- 初始化模块
if res ~= nil then
package.loaded[name] = res
end
end
return package.loaded[name]
end
- 如果
require
为指定模块找到了一个lua
文件,它就通过loadfile
方法来加载该文件。 - 如果是一个C程序库,就通过
loadlib
来加载。
loadfile和loadlib都只是加载了代码,而没有运行。
为了运行代码,require会以模块名作为参数来调用这些代码。
- 如果加载器有返回值,
require
就将这个返回值存储到table package.loaded
中,以此作为将来对同个模块调用的返回值。 - 如果加载器没有返回值,
require
就会返回table package.loaded
中的值。
一个模块还可以将返回给require的值直接放入package.loaded中
若要强制让require对同一个库加载两次的话,可以简单地删除package.loaded中的模块条目。
package.loaded["foo"] = nil
require "foo"
在搜索一个文件时,require
所使用的路径与传统路径也有所不同。require
采取的是一连串的模式(pattern),其中每一项都是一种将模块名转换为文件名的方式。
require
会用模块名替换每一个?
,然后根据替换后的结果来检查是否存在这样一个文件。如果不存在就会尝试下一项。
?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua
require用户搜索lua文件的路径存放在变量package.path中
在lua
中创建模块的最简单的方法就是创建一个table
,并将所有需要导出的函数放入其中,最后返回这个table
。
complex = {}
function complex.new (r, i) return { r = r, i = i} end
-- 定义一个常量 'i'
complex.i = complex.new(0, 1)
function complex.add(c1, c2)
return complex.new(c1.r + c2.r, c1.i + c2.i)
end
function complex.sub(c1, c2)
return complex.new(c1.r - c2.r, c1.i - c2.i)
end
function complex.mul(c1, c2)
return complex.new(c1.r * c2.r - c1.i * c2.i, c1.r * c2.r + c1.i * c2.r)
end
-- 声明为程序块内的局部变量
-- 其实就是定义为一个私有的变量
local function inv(c)
local n = c.r ^ 2 + c.i ^ 2
return complex.new(c.r / n, -c.i / n)
end
function complex.div(c1, c2)
return complex.new(mul, inv(c2))
end
return complex
创建模块的基本方法的缺点在于,他要求程序员他殴辱一些额外的关注。当访问统一模块中的其他公共实体时,必须限定其名称。并且,只要有一个函数的状态从私有改为共有(或从共有改为私有),就必须修改调用。另外,在私有声明中也很容易忘记关键字local
。
函数环境
是一种有趣的技术,他能够解决所有上述创建模块时遇到的问题。基本想法就是让模块主程序块有一个独占的环境。这样不仅他所有的函数都可功效这个table
,而且他的所有全局变量也都记录在这个table
中。还可以将所有共有函数声明为全局变量,这样他们就都自动记录在一个独立的table
中了。模块所要做的就是将这个table
赋予模块名和package.loaded
local modename = ...
local M = {}
_G[modename] = M
package.loaded[modename] = M
-- 必须先调用setmetatable在调用setfenv
-- 此时的模块中包含了所有的全局变量
setmetatable(M, {__index = _G})
setfenv(1, M)
-- 此时,声明函数add时,他就成了complex.add
function add(c1, c2)
return new(c1.r + c2.r, c1.i + c2.i)
end
另外还有一种更正规的方法,那些需要用到的函数或模块声明为局部变量
local modename = ...
local M = {}
_G[modename] = M
package.loaded[modename] = M
-- 导入段
-- 声明模块需要的外部的东西
local sqrt = math.sqrt
local io = io
-- 在此之后就不需要访问外部了
setfenv(1, M)
这种技术要求做更多的工作,但是他能清晰地说明模块的依赖性。同时,较之前的,这种速度更快。
local modname = ...
local M = {}
_G[modname] = M
package.loaded[modname] = M
<setup for external access>
setfenv(1, M)
lua
5.1中提供了一个新的函数module
,囊括了以上的功能。在开始编写一个模块时可以用以下代码来取代前面的设置代码:
module(...)
这句调用会创建一个新的table
,并将其赋予社党的全局变量和loaded table
,最后还有将这个table
设置为主程序块的环境。
默认情况下,module
不提供外部访问。必须在调用它前, 为需要访问的外部函数或模块声明适当的局部变量。也可以通过继承来实现外部访问,只需要在调用module
时加一个选项package.seeall
。等价于以下代码
setmetatable(M, {__index = _G})
只需要这么写
module(..., package.seeall)
lua
支持具有层级性的模块名,可以用一个点.
来分隔名称。
require "p.a"
-- 尝试打开以下文件
-- ./p/a.lua
-- /usr/local/lua/a/b.lua
-- /usr/local/lua/a/b/init.lua
require
模块a并不会自动地加载它的任何子模块。