Chapter 15: Modules and Packages
模块module 是一个库,可以被require 加载,并且module 里定义了一个全局表,所有模块要导出的东西,如函数、常量全部定义在这个表里面。和命名空间差不多。
require "mod"
mod.foo()
----------------
local m = require "mod"
m.foo()
----------------
require "mod"
local f = mod.foo
f()
15.1 The require Function
对require 来说,一个模块只是定义了许多values 的任意chunk。要加载一个模块只需简单的调用require "modname",它会返回一个包含了函数,全局变量等的表。但是,这个行为是模块完成的,不是require。所以,一些模块可能会选择返回其它的values 或是产生什么其它方面效果。
总是require 你需要的模块是一种良好的编程实践,既使你知道那些模块可能已经加载过了。
你应该将标准库排除在这条规则之外,因为它们由lua 预加载。虽然如此,一此人喜欢显示的去require:
local m = require "io"
m.write("hello world/n")
require 的实现
function require (name)
if not package.loaded[name] then -- module not loaded yet?
local loader = findloader(name)
if loader == nil then
error("unable to load module " .. name)
end
package.loaded[name] = true -- mark module as loaded
local res = loader(name) -- initialize module
if res ~= nil then
package.loaded[name] = res
end
end
return package.loaded[name]
end
require 首先在表package.loaded 中检查这个模块是否已经加载。如果是,就返回表中相应的值。因此,一旦模块已经加载,再次对它调用require 只会简单返回相同的值,不会重复加载。
如果一个模块还没被加载,require 试着为这个模块查找loader。它首先在package.preload 表中查找给定的库名,如果找到一个以库名命名的函数就把它作为加载器。如果找到的是lua 文件,就使用loadfile 加载。如果找到的是C library 就使用loadlib 加载。
让一个包被加载两次:
require "foo"
package.loaded["foo"] = nil
require "foo"
15.2 The Basic Approach for Writing Modules
Lua 中创建一个模块最简单的方法是:建一个表,放进所有相放的函数并返回这个表。注意我们如何将inv 作为私有名字,通过简单的将其声明为local。
这种“表模块”的使用方法不像真正模块。我们必须在每个函数前面加上模块名。我们可以改善这个问题,模块名作为局部名,然后将它赋值给全局名作为最终模块名:
local M = {}
complex = M -- module name
M.i = {r=0, i=1}
function M.new (r, i) return {r=r, i=i} end
function M.add (c1, c2)
return M.new(c1.r + c2.r, c1.i + c2.i)
end
实际上,我们完全不必写模块名,因为require 会把模块名作为参数传过来:
“消除”模块名
-- complex.lua
local modname = ...
local M = {}
_G[modname] = M
M.i = {r=0, i=1}
function M.new (r, i) return {r=r, i=i} end
function M.add (c1, c2)
return M.new(c1.r + c2.r, c1.i + c2.i) -- 后面有办法消除new 前的M
end
----------------------------------------------
-- t.lua
require "complex"
c = complex.new(2, 3)
print(c.i)
t.lua complex.lua 要放在同一个文件夹
这样,我们要改变模块名的时侯只需改变文件名。
消除返回语句
local modname = ...
local M = {}
_G[modname] = M
package.loaded[modname] = M
经过这个改进后,我们不再需要最后的返回语句了。记得,一个模块如果不返回一个值,require 就返回package.loaded[modname] 的当前值。
简单模块
complex = {}
function complex.new (r, i) return {r=r, i=i} end
-- defines a constant '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.i + 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.mul(c1, inv(c2))
end
return complex
15.3 Using Environments
消除模块问互相调用时的模块名
-- complex.lua
local modname = ...
local M = {}
_G[modname] = M
package.loaded[modname] = M
setfenv(1, M) -- 设置函数环境
M.i = {r=0, i=1}
function new (r, i) return {r=r, i=i} end
function add (c1, c2)
return new(c1.r + c2.r, c1.i + c2.i) -- 省了M.new
end
----------------------------------------------
-- t.lua
require "complex"
c = complex.new(2, 3)
c2 = complex.new(1, 2)
c3 = complex.add(c, c2)
print(c3.r.."+"..c3.i.."i")
利用函数环境使得同一模块中调用其它函数不再需要任何前缀。
但是失去了什么呢?setfenv 使得本模块无法访问其他模块,有许多方法可以恢复访问,最简单的方法是使用继承。
使用继承恢复由于setfenv后无法访问其它模块的问题
-- complex.lua
local modname = ...
local M = {}
_G[modname] = M
setmetatable(M, {__index = _G})
package.loaded[modname] = M
setfenv(1, M)
M.i = {r=0, i=1}
function new (r, i) return {r=r, i=i} end
function add (c1, c2)
math.floor(1.5) -- 访问外部模块
return new(c1.r + c2.r, c1.i + c2.i)
end
----------------------------------------------
-- t.lua
require "complex"
c = complex.new(2, 3)
c2 = complex.new(1, 2)
c3 = complex.add(c, c2)
print(c3.r.."+"..c3.i.."i")
(你必须在调用setfenv 之前调用setmetatable,想想为什么)
一个有趣的副作用是,现在模块包含了所有全局变量。例如,使用你的模块的用户可以这样访问标准函数sin:complex.math.sin(x) (Perl 的包也有这个特性)。
另一个快速访问其它模块的方法是声明一个局变量,它保存了“旧环境”:
使用局部变量保存全局变量的方法访问外部模块
-- complex.lua
local modname = ...
local M = {}
_G[modname] = M
package.loaded[modname] = M
local _G = _G
setfenv(1, M)
M.i = {r=0, i=1}
function new (r, i) return {r=r, i=i} end
function add (c1, c2)
_G.math.floor(1.5) -- 访问外部模块
return new(c1.r + c2.r, c1.i + c2.i)
end
----------------------------------------------
-- t.lua
require "complex"
c = complex.new(2, 3)
c2 = complex.new(1, 2)
c3 = complex.add(c, c2)
print(c3.r.."+"..c3.i.."i")
math.floor(1.5)
现在,对全局变量的访问必须加前缀_G,但是这样访问速度会快一点,因为不涉及元表。
更合理的方法是:
将需要访问的所有全局变量用局部变量保存起来
-- complex.lua
local modname = ...
local M = {}
_G[modname] = M
package.loaded[modname] = M
-- Import Section:
-- declare everything this module needs from outside
local floor = math.floor
local io = io
setfenv(1, M)
M.i = {r=0, i=1}
function new (r, i) return {r=r, i=i} end
function add (c1, c2)
floor(1.5) -- 访问外部模块
return new(c1.r + c2.r, c1.i + c2.i)
end
----------------------------------------------
-- t.lua
require "complex"
c = complex.new(2, 3)
c2 = complex.new(1, 2)
c3 = complex.add(c, c2)
print(c3.r.."+"..c3.i.."i")
math.floor(1.5)
这种方法要求做更多的工作,但是它更好的显示了模块间的依赖关系。而且运行速度比前面的方法都快。
15.4 The module Function
前面的例子需要重复写好多代码:
local modname = ...
local M = {}
_G[modname] = M
package.loaded[modname] = M
<setup for external access>
setfenv(1, M)
Lua 5.1 提供了一个module 函数代劳了这些代码的工作。只需把module 放在模块的前面:
module(...)
这个调用创建了一个表,把它赋值给一个合适的全局变量,并加载表,然后设置这个表为main chunk 的环境。
使用module 函数简化模块的写法
-- complex.lua
local floor = math.floor
module(...)
function new (r, i) return {r=r, i=i} end
function add (c1, c2)
floor(1.5) -- 访问外部模块
return new(c1.r + c2.r, c1.i + c2.i)
end
----------------------------------------------
-- t.lua
require "complex"
c = complex.new(2, 3)
c2 = complex.new(1, 2)
c3 = complex.add(c, c2)
print(c3.r.."+"..c3.i.."i")
math.floor(1.5)
module默认不提供外部访问:调用它之前,你必须声明适当的局部变量来保存你需要访问的所有外部变量。
要改变这一行为可以传一个可选参数package.seeall。
package.seeall 这一选项的功能等同于:setmetatable(M, {__index = _G})
因此,简单的在文件开头加入语句:module(..., package.seeall) 既可使一个文件变成模块;
你能编写类似正常Lua代码的其他任何东西。你不需要限定模块名或外部名。你不需要些模块名(实际上你甚至不需要知道模块名)。你不需要关心返回模块表。你所要做的只是加入那条单独的语句。
module函数提供一些额外的功能。多数模块不需要这些功能,但是一些发行包需要特殊的处理(举例来说,创建包含C函数和Lua函数的模块)。在创建模块表以前,module检查package.loaded是否已经包含了该模块的表,或是否已经存在给定名字的变量。如果在其中一个地方找到了表,module重用该表作为模块;这表明我们可以用module重新开始已经创建的模块。如果模块还不存在,则module就创建模块表。在那之后,它用一些预定义的变量组装表:_M包含模块表自身(它与_G等效);_NAME包含模块名(传入module的第一参数);以及_PACKAGE包含包名(不含最后组件的名字;见下一节)。
15.5 Submodules and Packages
将库放在单独的目录里
-- ./complexlib/complex.lua
local floor = math.floor
module(...)
function new (r, i) return {r=r, i=i} end
function add (c1, c2)
floor(1.5) -- 访问外部模块
return new(c1.r + c2.r, c1.i + c2.i)
end
----------------------------------------------
-- ./t.lua
require "complexlib.complex"
local complex = complexlib.complex
c = complex.new(2, 3)
c2 = complex.new(1, 2)
c3 = complex.add(c, c2)
print(c3.r.."+"..c3.i.."i")
math.floor(1.5)
以下翻译来自:http://blog.chinaunix.net/u1/57558/showart_1773927.html
Lua允许模块名为分层的,用点号分隔名字层次。例如,名为mod.sub的模块是mod的子模块。因此,你可以假定模块mod.sub会在表mod.sub内定义它的所有值,即,在表mod中以sub为键存储的表内。包是模块的完全树;它是Lua中的分发单元。
当你require名为mod.sub的模块时,require首先用原始模块名"mod.sub"作为键查询表package.loaded,然后是表package.preload;在该搜索中不考虑点号的意义。
raner,当查询定义了子模块的文件时,require把点号转换成另一个字符,通常是系统的目录分隔符(举例来说,Unix的'/'或Windows的'/')。在转换之后,require同任何其他名字一样搜索合成的名字。例如,假定路径
./?.lua;/usr/local/lua/?.lua;/usr/local/lua/?/init.lua
以及'/'作为目录分隔符,调用require "a.b"将试图打开下列文件:
./a/b.lua
/usr/local/lua/a/b.lua
/usr/local/lua/a/b/init.lua
该行为允许一个包的所有模块存在于单个目录中。例如,如果一个包有模块p、p.a和p.b,连同某个合适目录内的目录p,它们各自的文件可被命名为p/init.lua、p/a.lua和p/b.lua。
Lua用的目录分隔符是在编译时配置的,并且可为任何字符串(记住,Lua对目录一无所知)。例如,不带层次目录的系统可用'_'作为"目录"分隔符,这样require "a.b"将查找文件a_b.lua。
C函数名不能包含点号,所以子模块a.b的C库不能导出函数luaopen_a.b。此处require把点号转成其他字符,下划线。所以,名为a.b的C库应当命名其初始化函数为luaopen_a_b。此处我们也能用连字符技巧,附带着一些微妙的结果。例如,如果我们有个C库a并且我们像把它作为mod的子模块,我们可重名文件为mod/-a。当我们写require "mod.-a"时,require正确地找到了新文件mod/-a及其内的函数luaopen_a。
作为一个额外功能,require还有个用于加载C子模块的选项。当不能为子模块找到Lua文件或C文件时,它再次搜索C路径,但这次查找的是包名。例如,如果程序require子模块a.b.c,并且当查找a/b/c时require找不到文件,这个最后的搜索将查找a。如果用该名字找到C库,则require在该库中查找打开函数,本例中是luaopen_a_b_c。这个功能允许分发包把若干子模块合并放在单个C库中,每个带有自己的打开函数。
module函数也对子模块提供显式地支持。当我们用类似module("a.b.c")的调用创建子模块时,module把环境表放入变量a.b.c,即,放入表a的字段b中的表的字段c中。如果这些中间表中的任何一个不存在,module会创建它们。否则,它就重用它们。
从Lua的观点来看,相同包中的子模块没有显式地关系,除了它们的环境表可能是嵌套的。require模块a不会自动地加载它的子模块;类似地,require a.b不会自动地加载a。当然,如果需要,包的实现者可自由地创建这些连接。例如,独特的模块a可以由显式地require其一个或全部子模块开始。