Programming in Lua, 2Nd Edition - Chapter 15: Modules and Packages

 

 

 

Chapter 15: Modules and Packages

 

模块module 是一个库,可以被require 加载,并且module 里定义了一个全局表,所有模块要导出的东西,如函数、常量全部定义在这个表里面。和命名空间差不多。

 

require "mod"

mod.foo()

----------------

local m = require "mod"

m.foo()

----------------

require "mod"

local f = mod.foo

f()

 

 

15.1 The require Function

 

require 来说,一个模块只是定义了许多values 的任意chunk要加载一个模块只需简单的调用require "modname",它会返回一个包含了函数,全局变量等的表。但是,这个行为是模块完成的,不是require。所以,一些模块可能会选择返回其它的values 或是产生什么其它方面效果。

 

总是require 你需要的模块是一种良好的编程实践,既使你知道那些模块可能已经加载过了。

你应该将标准库排除在这条规则之外,因为它们由lua 预加载。虽然如此,一此人喜欢显示的去require

 

local m = require "io"

m.write("hello world/n")

 

require 的实现

 

function require (name)

       if not package.loaded[name] then -- module not loaded yet?

              local loader = findloader(name)

              if loader == nil then

                     error("unable to load module " .. name)

              end

              package.loaded[name] = true -- mark module as loaded

              local res = loader(name) -- initialize module

              if res ~= nil then

                     package.loaded[name] = res

              end

       end

       return package.loaded[name]

end

 

require 首先在表package.loaded 检查这个模块是否已经加载。如果是,就返回表中相应的值。因此,一旦模块已经加载,再次对它调用require 只会简单返回相同的值,不会重复加载。

 

如果一个模块还没被加载,require 试着为这个模块查找loader。它首先在package.preload 表中查找给定的库名,如果找到一个以库名命名的函数就把它作为加载器。如果找到的是lua 文件,就使用loadfile 加载。如果找到的是C library 就使用loadlib 加载。

 

让一个包被加载两次:

require "foo"

package.loaded["foo"] = nil

require "foo"

 

 

15.2 The Basic Approach for Writing Modules

 

Lua 中创建一个模块最简单的方法是:建一个表,放进所有相放的函数并返回这个表。注意我们如何将inv 作为私有名字,通过简单的将其声明为local

 

这种“表模块”的使用方法不像真正模块。我们必须在每个函数前面加上模块名。我们可以改善这个问题,模块名作为局部名,然后将它赋值给全局名作为最终模块名:

 

local M = {}

complex = M -- module name

M.i = {r=0, i=1}

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

 

function M.add (c1, c2)

       return M.new(c1.r + c2.r, c1.i + c2.i)

end

 


 

实际上,我们完全不必写模块名,因为require 会把模块名作为参数传过来

 

“消除”模块名

-- complex.lua

local modname = ...

local M = {}

_G[modname] = M

M.i = {r=0, i=1}

 

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

 

function M.add (c1, c2)

       return M.new(c1.r + c2.r, c1.i + c2.i)  -- 后面有办法消除new 前的M

end

----------------------------------------------

-- t.lua

require "complex"

c = complex.new(2, 3)

print(c.i)

 

t.lua complex.lua 要放在同一个文件夹

 

这样,我们要改变模块名的时侯只需改变文件名

 

消除返回语句

 

local modname = ...

local M = {}

_G[modname] = M

package.loaded[modname] = M

 

经过这个改进后,我们不再需要最后的返回语句了。记得,一个模块如果不返回一个值,require 就返回package.loaded[modname] 的当前值。

 

 

简单模块

 

complex = {}

 

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

 

-- defines a constant '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.i + 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.mul(c1, inv(c2))

end

 

return complex

 


 

15.3 Using Environments

 

消除模块问互相调用时的模块名

 

-- complex.lua

local modname = ...

local M = {}

_G[modname] = M

package.loaded[modname] = M

setfenv(1, M)  -- 设置函数环境

M.i = {r=0, i=1}

 

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

 

function add (c1, c2)

       return new(c1.r + c2.r, c1.i + c2.i)  -- 省了M.new

end

----------------------------------------------

-- t.lua

require "complex"

c  = complex.new(2, 3)

c2 = complex.new(1, 2)

c3 = complex.add(c, c2)

print(c3.r.."+"..c3.i.."i")

 

 

利用函数环境使得同一模块中调用其它函数不再需要任何前缀。

 

但是失去了什么呢?setfenv 使得本模块无法访问其他模块,有许多方法可以恢复访问,最简单的方法是使用继承。

 


 

使用继承恢复由于setfenv后无法访问其它模块的问题

 

-- complex.lua

local modname = ...

local M = {}

_G[modname] = M

setmetatable(M, {__index = _G})

package.loaded[modname] = M

setfenv(1, M)

M.i = {r=0, i=1}

 

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

 

function add (c1, c2)

       math.floor(1.5)   -- 访问外部模块

       return new(c1.r + c2.r, c1.i + c2.i)

end

----------------------------------------------

-- t.lua

require "complex"

c  = complex.new(2, 3)

c2 = complex.new(1, 2)

c3 = complex.add(c, c2)

print(c3.r.."+"..c3.i.."i")

 

(你必须在调用setfenv 之前调用setmetatable,想想为什么)

 

一个有趣的副作用是,现在模块包含了所有全局变量。例如,使用你的模块的用户可以这样访问标准函数sincomplex.math.sin(x) Perl 的包也有这个特性)。


 

另一个快速访问其它模块的方法是声明一个局变量,它保存了“旧环境”:

 

使用局部变量保存全局变量的方法访问外部模块

 

-- complex.lua

local modname = ...

local M = {}

_G[modname] = M

package.loaded[modname] = M

local _G = _G

setfenv(1, M)

M.i = {r=0, i=1}

 

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

 

function add (c1, c2)

       _G.math.floor(1.5)   -- 访问外部模块

       return new(c1.r + c2.r, c1.i + c2.i)

end

----------------------------------------------

-- t.lua

require "complex"

c  = complex.new(2, 3)

c2 = complex.new(1, 2)

c3 = complex.add(c, c2)

print(c3.r.."+"..c3.i.."i")

math.floor(1.5)

 

现在,对全局变量的访问必须加前缀_G,但是这样访问速度会快一点,因为不涉及元表。

 


 

更合理的方法是:

 

将需要访问的所有全局变量用局部变量保存起来

 

-- complex.lua

local modname = ...

local M = {}

_G[modname] = M

package.loaded[modname] = M

 

-- Import Section:

-- declare everything this module needs from outside

local floor = math.floor

local io = io

setfenv(1, M)

M.i = {r=0, i=1}

 

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

 

function add (c1, c2)

       floor(1.5)   -- 访问外部模块

       return new(c1.r + c2.r, c1.i + c2.i)

end

----------------------------------------------

-- t.lua

require "complex"

c  = complex.new(2, 3)

c2 = complex.new(1, 2)

c3 = complex.add(c, c2)

print(c3.r.."+"..c3.i.."i")

math.floor(1.5)

 

 

这种方法要求做更多的工作,但是它更好的显示了模块间的依赖关系。而且运行速度比前面的方法都快。

 


 

15.4 The module Function

 

前面的例子需要重复写好多代码:

 

local modname = ...

local M = {}

_G[modname] = M

package.loaded[modname] = M

<setup for external access>

setfenv(1, M)

 

Lua 5.1 提供了一个module 函数代劳了这些代码的工作。只需把module 放在模块的前面:

 

module(...)

 

这个调用创建了一个表,把它赋值给一个合适的全局变量,并加载表,然后设置这个表为main chunk 的环境。

使用module 函数简化模块的写法

 

-- complex.lua

local floor = math.floor

module(...)

 

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

 

function add (c1, c2)

       floor(1.5)   -- 访问外部模块

       return new(c1.r + c2.r, c1.i + c2.i)

end

----------------------------------------------

-- t.lua

require "complex"

c  = complex.new(2, 3)

c2 = complex.new(1, 2)

c3 = complex.add(c, c2)

print(c3.r.."+"..c3.i.."i")

math.floor(1.5)

 

module默认不提供外部访问:调用它之前,你必须声明适当的局部变量来保存你需要访问的所有外部变量

 

要改变这一行为可以传一个可选参数package.seeall

 

package.seeall 这一选项的功能等同于:setmetatable(M, {__index = _G})

 

因此,简单的在文件开头加入语句:module(..., package.seeall) 既可使一个文件变成模块;

 

你能编写类似正常Lua代码的其他任何东西。你不需要限定模块名或外部名。你不需要些模块名(实际上你甚至不需要知道模块名)。你不需要关心返回模块表。你所要做的只是加入那条单独的语句。

 

module函数提供一些额外的功能。多数模块不需要这些功能,但是一些发行包需要特殊的处理(举例来说,创建包含C函数和Lua函数的模块)。在创建模块表以前,module检查package.loaded是否已经包含了该模块的表,或是否已经存在给定名字的变量。如果在其中一个地方找到了表,module重用该表作为模块;这表明我们可以用module重新开始已经创建的模块。如果模块还不存在,则module就创建模块表。在那之后,它用一些预定义的变量组装表:_M包含模块表自身(它与_G等效);_NAME包含模块名(传入module的第一参数);以及_PACKAGE包含包名(不含最后组件的名字;见下一节)。

 


 

15.5 Submodules and Packages

 

将库放在单独的目录里

 

-- ./complexlib/complex.lua

local floor = math.floor

module(...)

 

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

 

function add (c1, c2)

       floor(1.5)   -- 访问外部模块

       return new(c1.r + c2.r, c1.i + c2.i)

end

----------------------------------------------

-- ./t.lua

require "complexlib.complex"

local complex = complexlib.complex

c  = complex.new(2, 3)

c2 = complex.new(1, 2)

c3 = complex.add(c, c2)

print(c3.r.."+"..c3.i.."i")

math.floor(1.5)

 

以下翻译来自:http://blog.chinaunix.net/u1/57558/showart_1773927.html

 

Lua允许模块名为分层的,用点号分隔名字层次。例如,名为mod.sub的模块是mod的子模块。因此,你可以假定模块mod.sub会在表mod.sub内定义它的所有值,即,在表mod中以sub为键存储的表内。包是模块的完全树;它是Lua中的分发单元。

 

当你require名为mod.sub的模块时,require首先用原始模块名"mod.sub"作为键查询表package.loaded,然后是表package.preload;在该搜索中不考虑点号的意义。

 

  raner,当查询定义了子模块的文件时,require把点号转换成另一个字符,通常是系统的目录分隔符(举例来说,Unix'/'Windows'/')。在转换之后,require同任何其他名字一样搜索合成的名字。例如,假定路径

 

./?.lua;/usr/local/lua/?.lua;/usr/local/lua/?/init.lua

 

以及'/'作为目录分隔符,调用require "a.b"将试图打开下列文件:

 

   ./a/b.lua
       /usr/local/lua/a/b.lua
       /usr/local/lua/a/b/init.lua

 

该行为允许一个包的所有模块存在于单个目录中。例如,如果一个包有模块pp.ap.b,连同某个合适目录内的目录p,它们各自的文件可被命名为p/init.luap/a.luap/b.lua

 

Lua用的目录分隔符是在编译时配置的,并且可为任何字符串(记住,Lua对目录一无所知)。例如,不带层次目录的系统可用'_'作为"目录"分隔符,这样require "a.b"将查找文件a_b.lua

 

  C函数名不能包含点号,所以子模块a.bC库不能导出函数luaopen_a.b。此处require把点号转成其他字符,下划线。所以,名为a.bC库应当命名其初始化函数为luaopen_a_b。此处我们也能用连字符技巧,附带着一些微妙的结果。例如,如果我们有个Ca并且我们像把它作为mod的子模块,我们可重名文件为mod/-a。当我们写require "mod.-a"时,require正确地找到了新文件mod/-a及其内的函数luaopen_a

 

作为一个额外功能,require还有个用于加载C子模块的选项。当不能为子模块找到Lua文件或C文件时,它再次搜索C路径,但这次查找的是包名。例如,如果程序require子模块a.b.c,并且当查找a/b/crequire找不到文件,这个最后的搜索将查找a。如果用该名字找到C库,则require在该库中查找打开函数,本例中是luaopen_a_b_c。这个功能允许分发包把若干子模块合并放在单个C库中,每个带有自己的打开函数。

 

module函数也对子模块提供显式地支持。当我们用类似module("a.b.c")的调用创建子模块时,module把环境表放入变量a.b.c,即,放入表a的字段b中的表的字段c中。如果这些中间表中的任何一个不存在,module会创建它们。否则,它就重用它们。

 

    Lua的观点来看,相同包中的子模块没有显式地关系,除了它们的环境表可能是嵌套的。require模块a不会自动地加载它的子模块;类似地,require a.b不会自动地加载a。当然,如果需要,包的实现者可自由地创建这些连接。例如,独特的模块a可以由显式地require其一个或全部子模块开始。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值