模块教程

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 函数。之所以使用空的“代理”表而不是直接给模块一个指向 _G__index 元方法以及一个 __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 之前将所有需要的标准函数存储在局部变量中,但这可能很麻烦,因此解决方案是 module 的第二个参数,它应该是一个函数,该函数以模块表作为参数被调用。package.seeall 会给模块一个带有 __index 元表的元表,该 __index 指向全局表,因此模块现在可以使用全局变量。问题是,模块的用户现在可以通过模块看到全局变量。

> require "mymodule"
> mymodule.foo()
Hello World!
> mymodule.print("example")
example

这充其量是奇怪且出乎意料的,最坏情况下可能是一个安全漏洞(如果您将模块提供给一个被沙盒化的脚本)。

module 被弃用的原因,除了上述问题之外,还在于它强制为模块用户指定了一个全局名称,并且在 5.2 中,一个函数无法更改其调用者的环境(至少在没有 debug 库的情况下),这使得 module 无法实现。

5.0 及更早的 5.1 模块使用此方法,但 5.2 及新的 5.1 模块应使用新方法(返回一个表)。

package 表

如上所述,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]

另请参阅


RecentChanges · preferences
编辑 · 历史
最后编辑于 2018 年 2 月 25 日下午 6:34 GMT (差异)