前言
从Lua5.1版本开始,就对模块和包添加了新的支持,可使用require和module来定义和使用模块和包。require用于使用模块,module用于创建模块。简单的说,一个模块就是一个程序库,可以通过require来加载,然后便得到了一个全局变量,表示一个table。这个table就像是一个命名空间,其内容就是模块中导出的所有东西,比如函数和常量,一个符合规范的模块还应使require返回这个table。
require函数
Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用require “<模块名>”就可以了。这个调用函数会返回一个由模块函数组成的table,并且还会定义一个包含该table的全局变量。但是,这些行为都是由模块完成的,而非require。所以,有些模块会选择返回其它值,或者具有其它的效果。那么require到底是如何加载模块的呢?
首先,要加载一个模块,就必须的知道这个模块在哪里。知道了这个模块在哪里以后,才能进行正确的加载。当我们写下require “mod”这样的代码以后,Lua是如何找这个mod的呢?
在搜索一个文件时,在windows上,很多都是根据windows的环境变量path来搜索。而require所使用的路径与传统的路径不同,require采用的路径是一连串的模式,其中每项都是一种将模块名转换为文件名的方式。require会用模块名来替换每个“?”,然后根据替换的结果来检查是否存在这样一个文件,如果不存在,就会尝试下一项。路径中的每一项都是以分号隔开,比如路径为以下字符串:
代码如下:
?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua
那么,当我们require “mod”时,就会尝试着打开以下文件:
mod
mod.lua
c:\windows\mod
/usr/local/lua/mod/mod.lua
可以看到,require函数只处理了分号和问号,其它的都是由路径自己定义的。在实际编程中,require用于搜索的Lua文件的路径存放在变量package.path中,在我的电脑上,print(package.path)会输出以下内容:
;.\?.lua;C:\Program Files (x86)\Lua\5.1\lua\?.lua;C:\Program Files (x86)\Lua\5.1\lua\?\init.lua;C:\Program Files (x86)\Lua\5.1\?.lua;C:\Program Files (x86)\Lua\5.1\?\init.lua;C:\Program Files (x86)\Lua\5.1\lua\?.luac;.\?.lua;C:\Program Files (x86)\Lua\5.1\lua\?.lua;C:\Program Files (x86)\Lua\5.1\lua\?\init.lua;C:\Program Files (x86)\Lua\5.1\?.lua;C:\Program Files (x86)\Lua\5.1\?\init.lua;;D:\Game\5.1\lua\?.luac
如果require无法找到与模块名相符的Lua文件,那Lua就会开始找C程序库;这个的搜索地址为package.cpath对应的地址,在我的电脑上,print(package.cpath)会输出以下值:
.\?.dll;.\?51.dll;C:\Program Files (x86)\Lua\5.1\?.dll;C:\Program Files (x86)\Lua\5.1\?51.dll;C:\Program Files (x86)\Lua\5.1\clibs\?.dll;C:\Program Files (x86)\Lua\5.1\clibs\?51.dll;C:\Program Files (x86)\Lua\5.1\loadall.dll;C:\Program Files (x86)\Lua\5.1\clibs\loadall.dll
当找到了这个文件以后,如果这个文件是一个Lua文件,它就通过loadfile来加载该文件;如果找到的是一个C程序库,就通过loadlib来加载。
loadfile和loadlib都只是加载了代码,并没有运行它们,为了运行代码,require会以模块名作为参数来调用这些代码。
如果lua文件和C程序库都找不到,怎么办?我们试一下,随便require一个东西,比如:
require "demo"
no field package.preload['demo']
no file '.\demo.lua'
no file 'C:\Program Files (x86)\Lua\5.1\lua\demo.lua'
no file 'C:\Program Files (x86)\Lua\5.1\lua\demo\init.lua'
no file 'C:\Program Files (x86)\Lua\5.1\demo.lua'
no file 'C:\Program Files (x86)\Lua\5.1\demo\init.lua'
no file 'C:\Program Files (x86)\Lua\5.1\lua\demo.luac'
no file '.\demo.lua'
no file 'C:\Program Files (x86)\Lua\5.1\lua\demo.lua'
no file 'C:\Program Files (x86)\Lua\5.1\lua\demo\init.lua'
no file 'C:\Program Files (x86)\Lua\5.1\demo.lua'
no file 'C:\Program Files (x86)\Lua\5.1\demo\init.lua'
no file 'D:\Game\5.1\lua\demo.luac'
no file '.\demo.dll'
no file '.\demo51.dll'
no file 'C:\Program Files (x86)\Lua\5.1\demo.dll'
no file 'C:\Program Files (x86)\Lua\5.1\demo51.dll'
no file 'C:\Program Files (x86)\Lua\5.1\clibs\demo.dll'
no file 'C:\Program Files (x86)\Lua\5.1\clibs\demo51.dll'
no file 'C:\Program Files (x86)\Lua\5.1\loadall.dll'
no file 'C:\Program Files (x86)\Lua\5.1\clibs\loadall.dll'
找不到就会提示报错!
package.loaded是什么?
require函数会将返回值存储到table package.loaded中;如果加载器没有返回值,require就会返回table package.loaded中的值。可以看到,我们上面的代码中,模块没有返回值,而是直接将模块名赋值给table package.loaded了。这说明package.loaded这个table中保存了已经加载的所有模块。
查看
require "main"
for k, v in pairs(package.loaded) do
print("loaded:" .. k .. "")
end
输出:
loaded:string
loaded:debug
loaded:package
loaded:_G
loaded:io
loaded:os
loaded:table
loaded:math
loaded:coroutine
loaded:main --这边出现了main文件名
现在我们就可以看看require到底是如何加载的呢?
在Lua编程中,通过`require`函数加载模块,这一过程涉及到搜索路径、lua和c库的加载机制。首先,我们来详细解析这两个关键方面。
1. 搜索路径
Lua的搜索路径由`package.path`变量管理,这是在Lua启动时根据环境变量`LUA_PATH`进行初始化的,多个路径之间用分号(`;`)隔开。例如,`package.path`可能设置为:
"./?.lua;./?.lc;/usr/local/?/init.lua"
这表示程序会首先尝试加载当前目录下的`.lua`和`.lc`文件,然后是`/usr/local`目录下的子目录及其`init.lua`文件。这个搜索顺序确保了Lua能够从常见的位置寻找所需的模块。
2. `require`加载过程
当调用`require`时,Lua会按照以下步骤寻找和加载模块:
检查缓存:`package.loaded[modname]`首先检查是否存在已经加载过的模块,如果有,直接返回该模块的值。
查找预加载器:如果`package.preload[modname]`存在,它通常是一个函数,该函数会被当作loader来执行,尝试加载模块。
路径加载器:
搜索lua文件:如果预加载器不存在,搜索将从`package.path`定义的路径开始,逐个查找lua文件,直到找到为止。
搜索c库:如果lua文件没有找到,搜索会转到`package.cpath`定义的c库路径,尝试动态链接和查找c函数作为loader。
默认加载器
所有-in-one-loader:如果以上步骤都无法找到合适的loader,可能会使用一个预设的统一加载器(`tryall-in-oneloader3`)。
加载和执行loader:一旦找到loader,require将`modname`模块名传递给加载器,并执行加载器。加载器函数负责执行模块的初始化和加载逻辑,通常会返回一个值,loader返回的值会被存储在`package.loaded[modname]`中,作为已加载模块的引用。如果loader本身没有返回值,`package.loaded[modname]`将被赋值为`true`。
cloader机制:当找到c库时,Lua会使用动态链接功能将应用与库连接起来,然后在库中寻找用于加载的C函数。这个C函数将成为实际的loader,负责执行模块的初始化和加载逻辑。
返回模块:最后,`require`返回`package.loaded[modname]`中的值,即加载的模块
总结来说,Lua的`require`加载机制既考虑了性能优化(缓存机制和预加载),又保证了灵活性(通过搜索路径扩展和c库的支持)。理解这些细节对于编写高效的Lua脚本以及处理模块加载异常至关重要。
lua打印table内容实例
-- @function: 打印table的内容,递归
-- @param: tbl 要打印的table
-- @param: level 递归的层数,默认不用传值进来
-- @param: filteDefault 是否过滤打印构造函数,默认为是
-- @return: return
function PrintTable( tbl , level, filteDefault)
local msg = ""
filteDefault = filteDefault or true --默认过滤关键字(DeleteMe, _class_type)
level = level or 1
local indent_str = ""
for i = 1, level do
indent_str = indent_str.." "
endprint(indent_str .. "{")
for k,v in pairs(tbl) do
if filteDefault then
if k ~= "_class_type" and k ~= "DeleteMe" then
local item_str = string.format("%s%s = %s", indent_str .. " ",tostring(k), tostring(v))
print(item_str)
if type(v) == "table" then
PrintTable(v, level + 1)
end
end
else
local item_str = string.format("%s%s = %s", indent_str .. " ",tostring(k), tostring(v))
print(item_str)
if type(v) == "table" then
PrintTable(v, level + 1)
end
end
end
print(indent_str .. "}")
endfor k, v in pairs(package) do
print("package["..k.."] type is "..type(v));
endprint("---------------------------------------------")
for k, v in pairs(package.loaded) do
print("package.loaded["..k.."] type is "..type(v));
endprint("---------------------------------------------")
PrintTable(package.loaded['table'])
封装一个函数,打印table数据的内容,这里打印package.loaded已加载所有模块及table的内容
package[preload] type is table
package[loadlib] type is function
package[loaded] type is table
package[loaders] type is table
package[cpath] type is string
package[config] type is string
package[path] type is string
package[seeall] type is function
---------------------------------------------
package.loaded[string] type is table
package.loaded[debug] type is table
package.loaded[package] type is table
package.loaded[_G] type is table
package.loaded[io] type is table
package.loaded[os] type is table
package.loaded[table] type is table
package.loaded[math] type is table
package.loaded[coroutine] type is table
---------------------------------------------
{
setn = function: 00C387F0
insert = function: 00C38B50
getn = function: 00C38A90
foreachi = function: 00C38A70
maxn = function: 00C38AD0
foreach = function: 00C38990
concat = function: 00C38CB0
sort = function: 00C38930
remove = function: 00C38570
}
require加载模块实例
testRuire.lua
print("load file testRequest.lua")
function testRq(x)
print("in file testRequire.lua, input param is: "..x)
endglobal = {}
global["testRq"] = testRq;
return global;
test.lua
#!/usr/local/bin/lua
print("package.loaded type is "..type(package.loaded));
local mytest = require("testRuire")for key, value in pairs(package.loaded)
do
print("package.loaded["..key.."] type is "..type(value));
endprint(mytest);
if type(mytest) == "table"
then
if type(mytest["testRq"]) == "function"
then
mytest["testRq"]("this is a test");
end
end
打印信息
package.loaded type is table
load file testRequest.lua
package.loaded[string] type is table
package.loaded[debug] type is table
package.loaded[package] type is table
package.loaded[_G] type is table
package.loaded[io] type is table
package.loaded[os] type is table
package.loaded[table] type is table
package.loaded[math] type is table
package.loaded[coroutine] type is table
package.loaded[testRuire] type is table
table: 00BFA0C0
in file testRequire.lua, input param is: this is a test
如果要清除模块
前面说过,require函数加载时会先检查 package.loaded 表里是不是已经有 modName 的表项了,如果有则直接返回表项的值,那如果已经加载了,想要清除模块,通过 package.loaded 修改加载模块的值为nil即可。
package.loaded["testRequire"] = nil;
参考:
lua require 层级 lua中的require_mob6454cc6bf0b7的技术博客_51CTO博客