再探Lua的require

本文详细探讨了Lua的require机制,从内部searchers的角度分析了如何加载lua和C模块。通过实验验证了package.preload、lua模块加载及C模块加载的searcher功能,并展示了如何自定义searchers以控制模块加载行为。同时,文章还讨论了模块的子模块加载以及如何防止模块定义全局变量。最后,基于这些理解,给出了一个重新实现require的示例。

再探Lua的require

我们之前实现了自己版本的require,首先认定为lua模块尝试加载,如果加载不成功则认定为C模块继续进行加载。实际上,在Lua内部,是通过searchers来区分不同的加载方式。Lua一共有4种searchers,用来加载lua模块的和加载C模块的分别是第2个和第3个。第1个searcher叫做preload,它使用package.preload这个内部table,根据加载模块的名称,去找到对应的加载函数来加载模块,例如:

-- mypackage.lua
print("hello world mypackage")

我们执行:

print(next(package.preload))
package.preload["mypackage"] = function(module) print("module ", module) end
require("mypackage")
require("mypackage")

输出如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4yhfitH7-1611326976644)(F:\Learning\blogs\再探Lua的require1.png)]

可以看到,默认情况下package.preload是一个空的table,我们为mypackge模块指定一个自定义的加载函数后,调用require就会调用我们自定义的函数了。同样地,如果这个函数没有返回值,lua会为之添加一个true的返回值,标记该模块已经加载过,无需重复加载。

我们还可以通过以下代码,来确认lua的第1个searcher为preload:

package.preload["mypackage"] = function(module) print("module ", module) end
package.searchers[1] = function(module) print("searchers 1 ", module) return function(module) print("my loader") end end
package.searchers[2] = nil
require("mypackage")

首先,package.searchers中的每一个searcher都要返回一个loader函数代表加载成功,否则即为加载失败,lua会依次在这个list中继续调用下一个searcher,直到返回一个loader函数。因此,这里我们显式地把package.searchers的长度缩减为1,避免lua调用其他searcher影响我们对结果的判断。输出如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E3oow1rq-1611326976648)(F:\Learning\blogs\再探Lua的require2.png)]

可以发现,package.preload中的函数并没有被调用,调用的实际上是我们重写过的package.searchers[1]。这就说明了第1个searcher即为preload。

同样地,我们可以用类似的手段,去发现第2个searcher是返回加载lua模块的loader,第3个searcher是返回加载C模块的loader:

package.searchers[2] = function(module) return function(module) print("my loader") end end
require("mypackage")

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uhsZV19O-1611326976652)(F:\Learning\blogs\再探Lua的require3.png)]

package.searchers[3] = function(module) return function(module) print("my loader") end end
-- WinFeature.dll
require("WinFeature")

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AyKvI48c-1611326976656)(F:\Learning\blogs\再探Lua的require4.png)]

有时,我们需要在一个模块中定义子模块,lua本身也支持了加载子模块的机制。模块的层级用.号来区分。例如我们尝试加载一个子模块:

require("a.b")

输出结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3yaF7Gho-1611326976657)(F:\Learning\blogs\再探Lua的require7.png)]

可以看到,如果是lua模块,那么子模块相当于父模块的子目录下。如果是C++模块,那么情况有些特殊,子模块除了可能存在父模块的子目录下,也有可能就和父模块在同一个dll中。负责搜索和父模块在同一个dll的searcher就是第4个searcher。类似我们也可以做出如下验证:

package.searchers[4] = nil
require("a.b")

执行,此时输出结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MWW1vvEU-1611326976658)(F:\Learning\blogs\再探Lua的require8.png)]

对比一下,即可得到答案。第4个searcher就是专门用来搜索包含多个子模块的dll的。该dll需要暴露给lua的导出函数名为luaopen_a_b

然后,让我们回到模块本身中来。一般,我们是这样编写一个模块的:

-- mypackage.lua
local M = {}

local privateField = 1
M.publicField = 1

local privateFunc = function()
    print("i am a private function")
end
M.publicFunc = function()
    print("i am a public function")
end

return M

local定义的都只在本模块中可见,可以认为是私有的成员和函数。我们执行:

m = require("mypackage")
print(m.privateField)
print(m.publicField)
m:privateFunc()
m:publicFunc()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZfZlsu3-1611326976659)(F:\Learning\blogs\再探Lua的require5.png)]

不过,如果我们在某个模块中定义了全局变量,在require之后,也会引入到调用require的环境中,这往往是我们不想要的:

-- mypackage.lua
local M = {}

globalField = 1
globalFunc = function()
    print("i am a global function")
end

return M

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O3DOpNnk-1611326976660)(F:\Learning\blogs\再探Lua的require6.png)]

我们之前在编写自己的require时提过,可以在loadFile函数传入一个环境参数,来禁止模块定义全局变量,这里我们可以灵活修改一下,让模块中的全局变量变成只属于该模块自身的一个变量。

最后,让我们根据lua的设计,重新实现一下require:

local env = {}
local searchers = {}

searchers[1] = function(module)
    local loader = package.preload[module]
    return loader
end

searchers[2] = function(module)
    module = string.gsub(module, '%.', '/')
    for pattern in string.gmatch(package.path, '[^;]+%?[^;]+') do
        local path = string.gsub(pattern, '%?', module)
        setmetatable(env, {__index = _G, __newindex = function(t, k, v) rawset(t, k, v) end})
        local loader = loadfile(path, nil, env)
        if loader then
            return loader
        end
    end
end

searchers[3] = function(module)
    module = string.gsub(module, '%.', '/')
    for pattern in string.gmatch(package.cpath, '[^;]+%?[^;]+') do
        local path = string.gsub(pattern, '%?', module)
        local loader = package.loadlib(path, "luaopen_" .. module)
        if loader then
            return loader
        end
    end
end

searchers[4] = function(module)
    local dotIndex = string.find(module, '%.')
    if not dotIndex then
        return
    end
    local lib = string.sub(module, 1, dotIndex - 1)
    module = string.gsub(module, '%.', '_')

    for pattern in string.gmatch(package.cpath, '[^;]+%?[^;]+') do
        local path = string.gsub(pattern, '%?', lib)
        local loader = package.loadlib(path, "luaopen_" .. module)
        if loader then
            return loader
        end
    end
end

function require_ex(module)
    if package.loaded[module] then
        return package.loaded[module]
    end

    env = {}

    for _, searcher in ipairs(searchers) do
        local loader = searcher(module)
        if loader and type(loader) == "function" then
            local ret = loader(module)
            if type(ret) ~= "table" then
                package.loaded[module] = env
            else
                for k, v in pairs(env) do
                    if ret[k] then
                        print("set multiple value")
                    else
                        ret[k] = v
                    end
                end
                package.loaded[module] = ret
            end
            return package.loaded[module]
        end
    end
end

如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路-

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MtArR4tE-1611327180622)(F:\Learning\blogs\qrcode_for_gh_16d0016744ab_258.jpg)]

内容概要:本文系统梳理了多个科研领域的前沿研究与技术实现,重点涵盖FDTD方法中的完美匹配层(PML)研究,以及Matlab/Simulink在电磁、电力、控制、通信、信号处理、图像处理、路径规划、能源系统优化等领域的仿真与算法实现。文中列举了大量基于Matlab和Python的科研案例,如风电功率预测、负荷预测、无人机三维路径规划、电池系统故障诊断、雷达模拟、通信编码、微电网优化调度等,并强调结合智能优化算法(如粒子群、遗传算法、深度学习等)提升系统性能。同时,提供了丰富的代码资源与仿真模型,涵盖永磁同步电机控制、逆变器设计、多智能体任务分配、虚拟电厂调度等复杂系统,助力科研人员快速开展复现实验与创新研究。; 适合人群:具备一定编程基础,熟悉Matlab/Python工具,从事电气工程、自动化、通信、人工智能、新能源、控制科学等相关领域研究的研发人员及研究生。; 使用场景及目标:① 学习并实现FDTD仿真中的PML边界条件以有效抑制数值反射;② 掌握Matlab/Simulink在多物理场建模、控制系统设计与优化算法中的综合应用;③ 借助提供的代码资源完成科研复现、课程设计、竞赛项目或工程原型开发; 阅读建议:此资源以科研实战为导向,不仅提供理论方法,更强调代码实现与仿真验证。建议读者结合自身研究方向,按目录顺序查阅相关模块,下载配套代码进行调试与二次开发,以达到学以致用、融会贯通的目的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值