Skip to content

Latest commit

 

History

History
199 lines (143 loc) · 6.08 KB

ch15.md

File metadata and controls

199 lines (143 loc) · 6.08 KB

第 15 章 模块与包

在用户的观点来看,一个模块就是一个程序库,可以通过require来加载,然后便得到一个全局变量,表示一个table。这个table就像是一个名称空间,其内容就是模块中导出的所有东西。

require(使用模块), module(创建模块)

-- 最简单的使用方法
require "mod"
mod.foo()
-- 可以为模块设置一个局部名称
local m = require "mod"
m.foo()
m.foo()

15.1 require 函数

对于require而言,一个模块就是一段定义了一些值的代码。

function require(name)
    if not package.loaded(name) then    -- 模块是否已加载?
        local loader = findloader(name)
        if loader == nil then
            error("unable to load module" .. name)
        end
        package.laoded[name] = true     -- 模块标记为已加载
        local res = loader(name)        -- 初始化模块
        if res ~= nil then
            package.loaded[name] = res 
        end
    end
    return package.loaded[name]
end

  • 如果require为指定模块找到了一个lua文件,它就通过loadfile方法来加载该文件。
  • 如果是一个C程序库,就通过loadlib来加载。

loadfile和loadlib都只是加载了代码,而没有运行。

为了运行代码,require会以模块名作为参数来调用这些代码。

  • 如果加载器有返回值,require就将这个返回值存储到table package.loaded中,以此作为将来对同个模块调用的返回值。
  • 如果加载器没有返回值,require就会返回table package.loaded中的值。

一个模块还可以将返回给require的值直接放入package.loaded中


若要强制让require对同一个库加载两次的话,可以简单地删除package.loaded中的模块条目。

package.loaded["foo"] = nil
require "foo"

在搜索一个文件时,require所使用的路径与传统路径也有所不同。require采取的是一连串的模式(pattern),其中每一项都是一种将模块名转换为文件名的方式。

require会用模块名替换每一个?,然后根据替换后的结果来检查是否存在这样一个文件。如果不存在就会尝试下一项。

?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua

require用户搜索lua文件的路径存放在变量package.path中

15.2 编写模块的基本方法

lua中创建模块的最简单的方法就是创建一个table,并将所有需要导出的函数放入其中,最后返回这个table

complex = {}

function complex.new (r, i) return { r = r, i = i} end

-- 定义一个常量 'i'
complex.i = complex.new(0, 1)

function complex.add(c1, c2)
    return complex.new(c1.r + c2.r, c1.i + c2.i)
end

function complex.sub(c1, c2)
    return complex.new(c1.r - c2.r, c1.i - c2.i)
end

function complex.mul(c1, c2)
    return complex.new(c1.r * c2.r - c1.i * c2.i, c1.r * c2.r + c1.i * c2.r)
end

-- 声明为程序块内的局部变量
-- 其实就是定义为一个私有的变量
local function inv(c)
    local n = c.r ^ 2 + c.i ^ 2
    return complex.new(c.r / n, -c.i / n)
end

function complex.div(c1, c2)
    return complex.new(mul, inv(c2))
end

return complex

15.3 使用环境

创建模块的基本方法的缺点在于,他要求程序员他殴辱一些额外的关注。当访问统一模块中的其他公共实体时,必须限定其名称。并且,只要有一个函数的状态从私有改为共有(或从共有改为私有),就必须修改调用。另外,在私有声明中也很容易忘记关键字local

函数环境是一种有趣的技术,他能够解决所有上述创建模块时遇到的问题。基本想法就是让模块主程序块有一个独占的环境。这样不仅他所有的函数都可功效这个table,而且他的所有全局变量也都记录在这个table中。还可以将所有共有函数声明为全局变量,这样他们就都自动记录在一个独立的table中了。模块所要做的就是将这个table赋予模块名和package.loaded

local modename = ...
local M = {}
_G[modename] = M
package.loaded[modename] = M

-- 必须先调用setmetatable在调用setfenv
-- 此时的模块中包含了所有的全局变量
setmetatable(M, {__index = _G})

setfenv(1, M)

-- 此时,声明函数add时,他就成了complex.add
function add(c1, c2)
    return new(c1.r + c2.r, c1.i + c2.i)
end

另外还有一种更正规的方法,那些需要用到的函数或模块声明为局部变量

local modename = ...
local M = {}
_G[modename] = M
package.loaded[modename] = M

-- 导入段
-- 声明模块需要的外部的东西
local sqrt = math.sqrt
local io = io

-- 在此之后就不需要访问外部了
setfenv(1, M)

这种技术要求做更多的工作,但是他能清晰地说明模块的依赖性。同时,较之前的,这种速度更快。

15.4 module函数

local modname = ...
local M = {}
_G[modname] = M
package.loaded[modname] = M
    <setup for external access>
setfenv(1, M)

lua5.1中提供了一个新的函数module,囊括了以上的功能。在开始编写一个模块时可以用以下代码来取代前面的设置代码:

module(...)

这句调用会创建一个新的table,并将其赋予社党的全局变量和loaded table,最后还有将这个table设置为主程序块的环境。

默认情况下,module不提供外部访问。必须在调用它前, 为需要访问的外部函数或模块声明适当的局部变量。也可以通过继承来实现外部访问,只需要在调用module时加一个选项package.seeall。等价于以下代码

setmetatable(M, {__index = _G})

只需要这么写

module(..., package.seeall)

15.5 子模块与包

lua支持具有层级性的模块名,可以用一个点.来分隔名称。

require "p.a"
-- 尝试打开以下文件
-- ./p/a.lua
-- /usr/local/lua/a/b.lua
-- /usr/local/lua/a/b/init.lua

require模块a并不会自动地加载它的任何子模块。