文章新地址
通常,Lua语言不会设置规则。相反,Lua语言提供的是足够强大的机制供不同的开发者实现最适合自己的规则。然而,这种方法对于模块而言并不是特别适用。模块系统的主要目标之一就是允许不同的人共享代码,缺乏公共规则就无法实现这样的分享。
Lua语言从5.1版本开始为模块和包定义了一系列的规则。这些规则不需要从语言中引入额外的功能,程序猿可以使用目前为止我们学习到的机制实现这些规则。程序员也可以自由地使用不同的策略。当然,不同的实现可能会导致程序不能使用外部模块,或者模块不能被外部程序使用。
从用户观点来看,一个模块就是一些代码,这些代码可以通过函数require加载,然后创建和返回一个表。这个表就像是某种命名空间,其中定义的内容是模块中导出的东西,比如函数和常量。
例如,所有的标准库都是模块。我们可以按照如下的方式使用数学库:
local m = require "math"
print(m.sin(3.14)) -- 0.0015926529164868
独立解释器会使用跟如下代码等价的方式提前加载所有标准库:
math = require "math"
string = require "string"
这种提前加载使得我们可以不用费劲地编写代码来加载模块math就可以直接使用函数math.sin。
使用表来实现模块的显著优点之一是,让我们可以像操作普通表那样操作模块,并且能利用Lua语言的所有功能实现额外的功能。在大多数语言中,模块不是第一类值(即它们不能被保存在变量中,也不能被当作参数传递给函数等),所以那些语言需要为模块实现一套专门的机制。而在Lua语言中,我们则可以轻易地实现这些功能。
例如,用户调用模块中的函数就有几种方法。其中常见的方法是:
local mod = require "mod"
mod.foo()
用户可以为模块设置一个局部名称:
local m = require "mod"
m.foo()
也可以为个别函数提供不同的名称:
local m = require "mod"
local f = m.foo
f()
还可以只引入特定的函数:
local f = require "mod".foo -- (require("mod")).foo
f()
上述这些方法的好处是无须语言的特别支持,它们使用的都是语言已经提供的功能。
函数 require
尽管函数require也只是一个没什么特殊之处的普通函数,但在Lua语言的模块实现中扮演者核心角色。要加载模块时,只需要简单地调用这个函数,然后传入模块作为参数。请记住,当函数的参数只有一个字符串常量时括号是可以省略的,而且一般在使用require时按照惯例也会省括号。不过尽管如此,下面这些用法也是正确的:
local m = require('math')
local modname = 'math'
local m = require(modname)
函数require尝试对模块的定义做最小的假设。对于函数来说,一个模块可以是定义了一些变量的代码。典型地,这些代码返回一个由模块中函数组成的表。不过,由于这个动作是由模块代码而不是由函数require完成的,所以某些模块可能会选择返回其他的值或者甚至引发副作用。
首先,函数require在表package.loaded中检查模块是否已被加载。如果模块已经被加载,函数require就返回相应的值。因此,一旦一个模块被加载过,后续的对于同一模块的所有require调用都将返回同一个值,而不会再运行任何代码。
如果模块尚未加载,那么函数require则搜索具有指定模块名的Lua文件(搜索路径有变量package.path指定)。如果函数require找到了相应的文件,那么就用函数loadfile将其进行加载,结果是一个我们称之为加载器的函数。
如果函数require找不到指定模块名的Lua文件,那么它就搜索相应名称的C标准库。如果找到了一个C标准库,则使用底层函数package.loadlib进行加载,这个底层函数会查找名为luaopen_modname的函数。在这种情况下,加载函数就是loadlib的执行结果,也就是一个被表示为Lua函数的C语言函数luaopen_modname。
不管模块是Lua文件还是C标准库中找到的,函数require此时都具有了用于加载它的加载函数。为了最终加载模块,函数require带着两个参数调用加载函数:模块名和加载函数所在文件名称。如果加载函数有返回值,那么函数require会返回这个值,然后将其保存在表package.loaded中,以便于将来在加载同一个模块时返回相同的值。如果加载函数么有返回值且表中的package.loaded【@rep{modname}]为空,函数require就假设模块的返回值是true。如果没有这种补偿,那么后续调用函数require时将会重复加载模块。
要强制函数require加载同一模块两次,可以先将模块从package.loaded中删除:
package.loaded.modname = nil
下一次在加