模块教程

lua-users home
wiki

创建和使用模块

实际上有两种方法可以创建模块,一种是 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]

另请参阅


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2018 年 2 月 26 日凌晨 12:34 GMT (差异)