Lua中的模块与包

原创 2017年01月03日 16:37:06

从用户观点来看,一个模块就是一个程序库,可以通过require来加载。然后就得到了一个全局变量,表示一个table。这个table就像是一个命名空间,其内容就是模块中导出的所有东西,例如函数和常量。一个规范的模块还应使require返回这个table。

例如,一个用户要想调用一个模块中的函数。最简单的方法是:

require "mod"
mod.foo()

如果希望使用较短的模块名称,则可以为模块设置一个局部名称:

local m=require "mod"
m.foo()


还可以为个别函数提供不同的名称:

require "mod"
local f=mod.foo
f()

1.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.loaded[name]=true         --将模块标记位已加载
		local res=loader(name)            --初始化模块
		if res~=nil then
			package.loaded[name]=res
		end
	end
	return package.loaded[name]
end



Lua提供了一个名为require的高层函数用来加载模块,但这个函数只假设了关于模块的基本概念。要加载一个模块,只需要简单的调用require<"模块名">。该调用会返回一个由模块函数组成的table,并且还会定义一个包含该table的全局变量。然而,这些函数都是由模块完成的,而非require。

即使知道某些用到的模块可能已经加载了,但只要用到require就是个良好的编程习惯。可以将标准库排除在此规则之外,因为Lua总是会预先加载它们。只要一个模块已加载过,后续的require调用都将返回同一个值,不会再次加载它。

如果require为指定模块找到了一个lua文件,它就会通过loadfile来加载该文件。而如果查找的是一个C程序库,就会通过loadlib来加载。注意,loadfile和loadlib都只是加载了代码并没有执行它们。为了运行代码,require会以模块名作为参数来调用这些代码。如果加载器有返回值,require就将这个返回值存储到table package.loaded中,以此作为将来对同一模块调用的返回值。如果加载器没有返回值,require就会返回table package.loaded中的值。

若要强制使require对同一个库加载两次的话,可以简单地删除package.loaded中的模块条目。例如:在成功地require "foo"后,package.loaded["foo"]就不为nil了,下面的代码可以再次加载该模块:

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


再搜索一个文件时,require所使用的路径与传统的路径有所不同。大部分程序所使用的路径就是一连串目录,指定了某个文件的具体位置。然而ANSI C却没有任何关于目录的概念。所以,require采用的路径是一连串的模式(pattern),模式的知识点这里传送门,其中每项都是一种将模块名转换为文件名的方式。进一步说,这种路径中的每项都是一个文件名,每项中还可以包含一个可选的问号。require会用模块名来替换每个问号,然后根据替换的结果来检查是否存在这样一个文件。如果不存在就尝试下一项。路径中的每项以分号隔开。例如:


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

sql
sql.lua
c:\windows\sql
/usr/local/lua/sql/sql.lua

上述替换为sql就得到了上述文件。

require只处理了分号和问号。

require用于搜索Lua文件的路径存放在变量package.path中,当lua启动后,便以环境变量LUA_PATH的值来初始化这个变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。


2.编写模块的基本方法

在Lua中创建一个模块最简单的方法是:创建一个table,并将所有需要导出的函数放入其中,最后返回这个table,以下代码演示这种方法。注意,将inv声明为程序块的局部变量,就是将其定义为一个私有的名称。

complex={}

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

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

3.module函数

编写一个简单的模块

Lua的模块是什么东西呢?通常我们可以理解为是一个table,这个table里有一些变量、一些函数…

等等,这不就是我们所熟悉的类吗?

没错,和类很像(实际上我说不出它们的区别)。
 
我们来看看一个简单的模块,新建一个文件,命名为game.lua,代码如下:

复制代码代码如下:

game = {}
function game.play()
    print("那么,开始吧");
end
function game.quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return game;

我们定义了一个table,并且给这个table加了两个字段,只不过这两个字段的值是函数而已。

至于如何使用模块,那就要用到我们之前介绍过的require了。
 
我们在main函数里这么使用:

复制代码代码如下:

local function main()
    cc.FileUtils:getInstance():addSearchPath("src")
    game = require("game");
   
    game.play();
end


注意,我们要require其他文件的时候,要把文件路径给设置好,否则会找不到文件。
因为我使用的是Cocos Code IDE,直接调用addSearchPath函数就可以了,我的game.lua文件是在src目录下的。
 
好了,运行代码,结果如下:

复制代码代码如下:

[LUA-print] 那么,开始吧

OK,这就是一个很简单的模块,如果我们习惯了Java、C++等面向对象语言,那也可以简单地把模块理解为类。

2.为以后的自己偷懒——避免修改每个函数中的模块名

假设我们想把刚刚的game模块改个名字,改成eatDaddyGame,那么,我们需要做以下两件事情:

1).修改game.lua的文件名
2).修改game.lua的内容,把所有的game改成eatDaddyGame
 
目前的game.lua函数还算少,就两个,实际上一个模块的函数肯定不会少的,那么,要这么去改这些函数,太烦了。

如果批量修改,又怕有哪个地方改错。

于是,我们可以这么偷懒:

复制代码代码如下:

game = {}
local M = game;
function M.play()
    print("那么,开始吧");
end
function M.quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

我们用一个局部变量M来代替了game,于是,以后我们只需要修改前面两个的game就可以了,函数部分的内容完全不需要去修改。

这个偷懒其实蛮有用的,某些情况下,修改越少,越安全~

3.更进一步的偷懒——模块名参数

实际上,我们可以更加得偷懒,以后修改模块名,只需要修改模块的文件名就可以了,文件内容可以不管,具体怎么实现?

看代码:

复制代码代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
function M.play()
    print("那么,开始吧");
end
function M.quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

留意一下,这里有一个 local modelName = …
“…”就是传递给模块的模块名,在这里其实就是“game”这个字符串。
 
接着,有点微妙了,还记得之前介绍的全局环境_G吗?我们以”game”作为字段名,添加到_G这个table里。

于是,当我们直接调用game的时候,其实就是在调用_G["game"]的内容了,而这个内容就是这里的M。
 
能逻辑过来吗?就是这么简单,在你没有忘记_G的前提下~

4.利用非全局环境制作更简洁和安全的模块

如果说,刚刚已经达到了我们作为高(ai)智(zhe)商(teng)人群的巅峰,那,你就太天真了。

巅峰就是要拿来超越的,还记得我们的非全局环境吗?就是那个setfenv函数。
 
我们来看看下面的代码:

复制代码代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
setfenv(1, M);
function play()
    print("那么,开始吧");
end
function quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

我们把game.lua这个模块里的全局环境设置为M,于是,我们直接定义函数的时候,不需要再带M前缀。

因为此时的全局环境就是M,不带前缀去定义变量,就是全局变量,这时的全局变量是保存在M里。

所以,实际上,play和quit函数仍然是在M这个table里。
 
于是,我们连前缀都不用写了,这真是懒到了一个极致,简直就是艺术~

另外,由于当前的全局环境是M,所以, 在这里不需要担心重新定义了已存在的函数名,因为外部的全局变量与这里无关了。
 
当然,如果大家现在就运行代码,肯定会报错了。

因为我们的全局环境改变了,所以print函数也找不到了。

为了解决这个问题,我们看看第5条内容吧~

5.解决原全局变量的无法找到的问题——方案1

第一个方法,就是我们之前介绍过的,使用继承,如下代码:

复制代码代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
-- 方法1:使用继承
setmetatable(M, {__index = _G});
setfenv(1, M);
function play()
    print("那么,开始吧");
end
function quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

没错,使用__index元方法就能解决这个问题了,当找不到print等函数时,就会去原来的_G里查找。

6.解决原全局变量的无法找到的问题——方案2

第二个方法更简单,使用一个局部变量把原来的_G保存起来,如下代码:

复制代码代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
-- 方法2:使用局部变量保存_G
local _G = _G;
setfenv(1, M);
function play()
    _G.print("那么,开始吧");
end
function quit()
    _G.print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

这种方法的缺点比较明显,那就是,每次调用print等函数时,都要使用_G前缀。

7.解决原全局变量的无法找到的问题——方案3

第三个方法比较繁琐,使用局部变量把需要用到的其他模块保存起来,如下代码:

复制代码代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
-- 方法3:保存需要使用到的模块
local print = print;
setfenv(1, M);
function play()
    print("那么,开始吧");
end
function quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end
return M;

这种方法的缺点更明显了,所有用到的模块都要用局部变量声明一次,烦人。
 
但,就速度而言,第三种方案比第二种方案快,第二种方法又比第一种快。
但至于快多少,我也不知道,只是理论上~我也没测试。

8.你就笑吧,但,我还想更加偷懒——module函数

本以为刚刚介绍的那些技巧已经够偷懒的吧?
但Lua似乎知道我们有多懒似的,它竟然把我们把这一切都自动完成了。
再来回忆我们刚刚为了偷懒而写的几句代码:

复制代码代码如下:

local M = {};
local modelName = ...;
_G[modelName] = M;
setmetatable(M, {__index = _G});
setfenv(1, M);

就这几句代码,其实我们可以忽略不写,因为,我们有module函数,它的功能就相当于写了这些代码。
我们修改一下game.lua的内容,如下代码:

复制代码代码如下:

module(..., package.seeall);
function play()
    print("那么,开始吧");
end
function quit()
    print("你走吧,我保证你不会出事的,呵,呵呵");
end

注意,前面的几行代码都没了,只留下了一个module函数的调用。

module函数的调用已经相当于之前的那些代码了。

而package.seeall参数的作用就是让原来的_G依然生效,相当于调用了:setmetatable(M, {__index = _G});
 
再次留意一下,代码末尾的return M也不见了,因为module函数的存在,已经不需要我们主动去返回这个模块的table了。


版权声明:本文为博主原创文章,未经博主允许不得转载。(讨论问题+Q763949771)

相关文章推荐

Lua中的模块以及实现方法

从使用的角度来看,一个模块就是一个程序库,可以通过Lua自身提供的require来加载。然后便得到一个全局变量,表示一个table。这个table就是像一个名字空间,其内容就是模块导出的所有东西,例如...

luasql编译集成方式

之前由于skynet框架本身集成了mysql模块,因此没有碰到什么困难就完成了mysql功能的开发。这次由于没有采用skynet框架,因此在编译集成lua下的mysql时遇到了几个问题,记录下: 1...

lua5.1.4中实现自定义require的loader函数

刚开始以为只是简单的把自己的C函数,替换到package.loaders[2](索引2是lua的文件加载器)里面就OK了,实际上也是这样的,但是这样的代价就是文件搜索啊,一堆一堆的判断啊都要自己做,因...

Lua5.1编程二:Lua高级特性

1 数据结构 1.1 数组 对table使用数字索引,即把table当数组使用。约定数组下标从1开始。 若索引中间有空洞,table并不会为空洞索引分配空间。 只有当表是顺序表时,#tab...
  • zzulp
  • zzulp
  • 2014-04-02 14:30
  • 2550

Lua中的模块与module函数

这篇文章主要介绍了Lua中的模块(module)和包(package)详解,本文讲解了require函数、写一个模块、package.loaded、module函数等内容.    从Lua5.1版本开...

Lua中的模式

在Lua中有很多字符分类: . 所有字符 %a 字母 %c 控制字符 %d 数字 %l 小写字母 %p 标点符号 ...

Lua学习笔记--模块与包

学习完了函数,那么,一堆函数就成了一个模块,一堆模块就是一个包。今天来学习一下怎么写一个模块和怎么调用模块。 一.简介 Lua的感觉就是简洁,自由,一个万能的table可以搞定所有的事情。Lua从...

Lua中的模块(module)和包(package)详解

前言 从Lua5.1版本开始,就对模块和包添加了新的支持,可是使用require和module来定义和使用模块和包。require用于使用模块,module用于创建模块。简单的说,一个模块就是一个程序...

Lua自己实现载入一个模块

当在模块名前面有一个"." 时,import() 会从当前模块所在目录中查找其他模块。因此 MyClass 及其相关文件不管存放到什么目录里,我们都不再需要修改 MyClass 中的 import()...

lua中的require机制

为了方便代码管理,通常会把lua代码分成不同的模块,然后在通过require函数把它们加载进来。 现在看看lua的require的处理流程。
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)