模块教程 |
|
实际上有两种方法可以创建模块,一种是 5.1 和 5.2 的新方法,另一种是 5.0 和早期 5.1 的旧方法(已弃用)。我们首先介绍新方法,然后讨论 Lua 5.0 和 5.1 的旧方法。
创建一个名为 mymodule.lua
的示例文件,内容如下
local mymodule = {} function mymodule.foo() print("Hello World!") end return mymodule
现在,要在交互式解释器中使用此新模块,只需执行以下操作
> mymodule = require "mymodule" > mymodule.foo() Hello World!
在实际的脚本文件中,建议将 mymodule
变量设为局部变量
local mymodule = require "mymodule" mymodule.foo()
但由于我们在交互式解释器中,这会导致它在下一行超出范围,因此我们必须将其放入全局变量中。
为了能够在不同的文件中重复使用同一个模块而无需重新运行模块代码,Lua 会将模块缓存到 package.loaded
表中。为了演示这一点,假设我们更改了 mymodule.lua
中的 foo
,使其现在打印 "Hello Module!"
。如果我们继续在上面的同一个会话中,将会发生以下情况
> mymodule = require "mymodule" > mymodule.foo() Hello World!
要真正重新加载模块,我们需要先将其从缓存中删除
> package.loaded.mymodule = nil > mymodule = require "mymodule" > mymodule.foo() Hello Module!
另一个好处是,由于它们是存储在变量中的普通表,因此模块可以任意命名。假设我们认为 "mymodule"
太长,每次使用其中的函数时都要输入它
> m = require "mymodule" > m.foo() Hello Module!
有不同的方法可以组合模块表,您可以根据自己的情况和个人喜好进行选择
在顶部创建一个表,并将您的函数添加到其中
local mymodule = {} local function private() print("in private function") end function mymodule.foo() print("Hello World!") end function mymodule.bar() private() mymodule.foo() -- need to prefix function call with module end return mymodule
将所有函数设为局部变量,并将它们放在表末尾
local function private() print("in private function") end local function foo() print("Hello World!") end local function bar() private() foo() -- do not prefix function call with module end return { foo = foo, bar = bar, }
将上面两个示例结合起来
local mymodule = {} local function private() print("in private function") end local function foo() print("Hello World!") end mymodule.foo = foo local function bar() private() foo() end mymodule.bar = bar return mymodule
您甚至可以更改代码块的环境,将您创建的任何全局变量存储到模块中
local print = print -- the new env will prevent you from seeing global variables local M = {} if setfenv then setfenv(1, M) -- for 5.1 else _ENV = M -- for 5.2 end local function private() print("in private function") end function foo() print("Hello World!") end function bar() private() foo() end return M
或者,如果您不想将所有需要的全局变量保存到局部变量中
local M = {} do local globaltbl = _G local newenv = setmetatable({}, { __index = function (t, k) local v = M[k] if v == nil then return globaltbl[k] end return v end, __newindex = M, }) if setfenv then setfenv(1, newenv) -- for 5.1 else _ENV = newenv -- for 5.2 end end local function private() print("in private function") end function foo() print("Hello World!") end function bar() private() foo() end return M
请注意,这可能会使访问全局变量和模块变量的速度略微变慢,因为它使用了 __index
函数。使用空“代理”表而不是给模块一个 __index
元方法以及一个指向 _G
的 __newindex
元方法的原因是,这会导致以下情况
> mymodule = require "mymodule" > mymodule.foo() Hello World! > mymodule.print("example") -- unwanted __index metamethod example
Lua 5.0 和 5.1 有一个module
函数,使用方法如下
mymodule.lua
:
module("mymodule", package.seeall) function foo() -- create it as if it's a global function print("Hello World!") end
使用方法如下
> require "mymodule" > mymodule.foo() Hello World!
它的工作原理是为模块创建一个新表,将其存储在由module
的第一个参数命名的全局变量中,并将其设置为代码块的环境,因此如果您创建了一个全局变量,它将存储在模块表中。
这将使模块无法看到全局变量(如print
)。一种解决方案是在调用模块之前将所有需要的标准函数存储在局部变量中,但这可能很繁琐,因此解决方案是module
的第二个参数,它应该是一个以模块表作为参数调用的函数。package.seeall
为模块提供了一个元表,该元表有一个指向全局表的__index
,因此模块现在可以使用全局变量。问题是模块的用户现在可以通过模块看到全局变量
> require "mymodule" > mymodule.foo() Hello World! > mymodule.print("example") example
这充其量是奇怪且出乎意料的,最坏情况下可能是一个安全漏洞(如果您将模块提供给沙盒脚本)。
除了上述问题之外,module
被弃用的原因是它强制将全局名称强加给模块的用户,以及在 5.2 中,函数无法更改其调用者的环境(至少没有使用调试库),这使得module
无法实现。
5.0 和更早版本的模块使用此方法,但 5.2 和新的 5.1 模块应该使用新方法(返回一个表)。
如上所述,Lua 使用package
库来管理模块。
package.path
(用于用 Lua 编写的模块)和package.cpath
(用于用 C 编写的模块)是 Lua 搜索模块的位置。它们是分号分隔的列表,每个条目中都可以包含一个?
,它将被模块名称替换。以下是一个示例
> =package.path ./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua > =package.cpath ./?.so;/usr/local/lib/lua/5.1/?.so;/usr/lib/x86_64-linux-gnu/lua/5.1/?.so;/usr/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so
package.loaded
是一个表,其中已加载的模块按名称存储。如前所述,它充当缓存,以防止模块被加载两次,而是require
首先尝试从该表中获取模块,如果它是 false 或 nil,则尝试加载。
package.preload
是一个与模块名称关联的函数表。在搜索文件系统之前,require
会检查package.preload
是否具有匹配的键。如果是,则调用该函数,并将它的结果用作require
的返回值。
其他字段对于 Lua 模块的常规使用并不重要,但如果您对模块系统的工作原理感兴趣,则手册中对其进行了详细描述:[1]