从Lua5.1版本开始,就对模块/包添加了新的支持,可是使用require函数和package函数来加载模块,使用table模拟module来定义模块。
函数require用于加载模块,module用于创建模块。
对开发来说,使用module可以有效分隔代码,实现代码共享,便于代码管理。
对于用户来说,一个module相当于一个Xnix中的共享库so或者windows中的动态库dll。
lua是通过table来实现模块的,典型的写法如下。
定义一个lua模块文件 – moduleA.lua
-- moduleA.lua
-- 通常是加local的,加了local是局部变量,需要return一下。
-- 如果不加,则M默认注册到_G中,require后,即使不return也可以直接使用M。
local M = {} -- 通过table来实现模块
M.work = function(...)
print("function working")
for i, v in ipairs{...} do
print(i, v)
end
-- do some job.
end
return M
这里定义了一个具有变长参数的函数–其函数参数个数可变。参数的定义形式为...
,在内部访问时,也使用{...}
,和使用table的方式一样。
要使用定义好的module,使用require函数,加载定义的module,然后就可以使用。
-- test.lua
local m = require "moduleA"
m.work('a','b',1,2,3)
要加载一个模块,就必须的知道这个模块在哪里。知道了这个模块在哪里以后,才能进行正确的加载。当我们写下require “mod”这样的代码以后,Lua是如何找这个mod的呢?
在搜索一个文件时,在windows上,很多都是根据windows的环境变量path来搜索,而require所使用的路径与传统的路径不同,require采用的路径是一连串的模式,其中每项都是一种将模块名转换为文件名的方式。require会用模块名来替换每个“?”,然后根据替换的结果来检查是否存在这样一个文件,如果不存在,就会尝试下一项。路径中的每一项都是以分号隔开。而且lua模块包括lua模块,以及c实现的模块。其对于require用于搜索的Lua文件的路径存放在变量package.path和package.cpath中。我们可以在输出看看。
检查一下lua系统中的环境,执行命令:
$ lua
Lua 5.2.4 Copyright (C) 1994-2015 Lua.org, PUC-Rio
stdin:1: unexpected symbol near char(228)
> print(package.path)
/usr/local/share/lua/5.2/?.lua;/usr/local/share/lua/5.2/?/init.lua;/usr/local/lib/lua/5.2/?.lua;/usr/local/lib/lua/5.2/?/init.lua;./?.lua
> print(package.cpath)
/usr/local/lib/lua/5.2/?.so;/usr/local/lib/lua/5.2/loadall.so;./?.so
可以再看看各种函数和对象的类型
> print(print)
function: 0x104cb304d
> print(require)
function: 0x7fdc3b403db0
> print(package)
table: 0x7fdc3b403740
> print(_G)
table: 0x7fdc3b4028d0
> print(module)
function: 0x7fdc3b403d60
>
function require(name)
if not packge.loaded[name] then ---- 避免重复加载
local loader = findloader(name) ---- 如果是so,就以`loadlib`方式加载文件,如果是lua文件,就以`loadfile`方式加载文件。
if loader == nil then
error("unable to load module " .. name)
end
package.loaded[name] = true
-- 将模块标记为以加载,我们有时候会看到require返回true的现象,是由于被调用的模块,没有显示的执行package.loaded[modname] = M
-- 或者给出return M这样的返回值。
local res = loader(name)
-- require会以name作为入参来执行该文件,如果有返回结果,就将返回结果保存在package.loaded[name]中,
-- 如果没有返回结果,就直接返回package.loaded[name]。如果我们在被调用的文件中直接写明return 1。
-- 则调用者的require的返回结果就是1。但是只要我们显示的在require文件中写明了_G[modname] = M,
-- 我们仍然可以在require之后,直接使用M作为名字来调用,是由于将M加入到了_G中。
if res ~= nil then
package.loaded[name] = res
end
end
return package.loaded[name]
end
传参: require会将模块名作为参数传递给模块
返回值:如果一个模块没有返回值的话,require就会返回package.loaded[modulename]
作为返回值。
require会将返回值存储到package.loaded
–一个table– 中;如果加载器没有返回值,require就会返回table package.loaded
中的值。可以看到,我们上面的代码中,模块没有返回值,而是直接将模块名赋值给table package.loaded了
。这说明package.loaded
这个table中保存了已经加载的所有模块。
现在我们就可以看看require到底是如何加载的呢?
1.先判断package.loaded
这个table中有没有对应模块的信息;
2.如果有,就直接返回对应的模块,不再进行第二次加载;
3.如果没有,就加载,返回加载后的模块。
lua用_G一张表保存了全局数据(变量,函数和表等)。
我们在lua中定义一个module,如果不加local
,则它是一个注册在全局下的表。我们通过加local避免了它在污染全局表空间,只在本文件生效。如果我们没有将其注册到_G
下,在其他文件是无法直接通过他的原始名字来访问的。这里有一个不便利的地方,每个函数前面都要带M
,M
的下的函数相互访问也要带M
头。
解决方法:通过setfenv
local modname = ...
local M = {}
_G[modname] = M
package.loaded[modname] = M
setfenv(1, M)
后续的函数直接定义名字,因为他们的环境空间已经由_G
改为了M
。
如果要使用全局函数,则可以本地额外增加一条local _G = _G
或者setmetatable(M, {__index = G})
。
更好的方法是在setfenv之前将需要的函数都保存起来,local sqrt = math.sqrt
在定义一个模块时,前面的几句代码都是一样的,就分为以下几步:
1.从require传入的参数中获取模块名;
2.建立一个空table;
3.在全局环境_G中添加模块名对应的字段,将空table赋值给这个字段;
4.在已经加载table中设置该模块;
5.设置环境变量。
就是这几步,在每一个模块的定义之前都需要加上,有点麻烦,在Lua5.1中提供了一个新函数module,它替代我们完成以上这些步骤完成的功能。
在编写一个模块时,可以直接用以下代码来取代前面的设置代码:
local moduleName = ...
local M = {} -- 局部的变量
_G[moduleName] = M -- 将这个局部变量最终赋值给模块名
package.loaded[moduleName] = M
local sqrt = math.sqrt -- 在我们自己的模块中需要用到math.sqrt这个函数,所以就先保存下来
local io = io -- 需要用到io库,也保存下来
setfenv(1, M) -- 设置完成以后,就不能再使用_G table中的内容了
等同于
module(modname)。
默认的情况下,module不提供外部的访问的,也就是说,你无法访问前一个环境了。如果要访问外部变量,两种方法:
1.在声明module之前,local 变量 = 外部变量
2.使用module(modname, package.seeall)
, 等价于setmetatable(M, __index = _G)
在使用module时是这样解决的:
module(..., package.seeall)
其功能就好比之前的功能再加上了setmetatable(M, {__index = _G})
。有了这一句代码,基本上就可以说万事不愁了。
网上一个小示例:
先定义一个module:
mypack.lua
--mypack.lua
module(..., package.seeall) --定义module
ver = "0.1 alpha"
function aFunInMyPack()
print("Hello!")
end
_G.aFuncFromMyPack = aFunInMyPack
测试代码test.lua:
--testP.lua:
pack = require "mypack" --导入module
print(ver or "No ver defined!")
print(pack.ver)
print(aFunInMyPack or "No aFunInMyPack defined!")
pack.aFunInMyPack()
print(aFuncFromMyPack or "No aFuncFromMyPack defined!")
aFuncFromMyPack()
执行结果:
$lua test.lua
No ver defined!
0.1 alpha
No aFunInMyPack defined!
Hello!
function: 0x7fe1e1501390
Hello!
如果要更好地组织代码,可以把不同的模块放到不同的目录层次。
比如
ls -l -R
total 8
drwxr-xr-x 3 david staff 96 3 23 12:07 sub
-rw-r--r--@ 1 david staff 288 3 23 12:50 test.lua
./sub:
total 8
-rw-r--r--@ 1 david staff 174 3 23 12:11 mypack.lua
在工作执行目录下,包括了test.lua,以及sub目录,其下放置了mypack.lua – 对应mypack模块。
则test.lua中require时,需要包括模块目录形式,require "sub.mypack"
注意这里使用的是.
,而不是\
。