Lua笔记——4.Package

module简介

Lua 5.1 加入模块管理机制module,类似于Java的packages、C++的namespaces,可以通过require用于加载模块,module用于创建模块。require加载一个自定义或者第三方的module,然后便得到了一个全局变量,表示一个table。

Lua 5.2 之后则去掉了module创建模块的函数,仅保留requir加载函数在全局环境

require函数

创建模块

在Lua中创建一个模块最简单的方法:创建一个table,并将所有需要导出的函数放入其中,最后返回这个table,相当于将导出的函数作为table的一个字段。

创建一个简单模块,代码如下:

--file: simpleMod.lua

local _Mod = {}

_Mod._Mod = _Mod

function _Mod:New(name)
    local mod = {}
    mod.Name = name or "Default Name"
    mod._VERSION = 0.01
    return setmetatable(mod , {__index = self})
end

function _Mod:Update()
    self._VERSION = self._VERSION + 0.01
    return self._VERSION
end

return _Mod

调用模块

require (modname)

Loads the given module. The function starts by looking into the package.loaded table to determine whether modname is already loaded. If it is, then require returns the value stored at package.loaded[modname]. Otherwise, it tries to find a loader for the module.

To find a loader, require is guided by the package.loaders array.(package.loaders array:A table used by require to control how to load modules.) By changing this array, we can change how require looks for a module. The following explanation is based on the default configuration for package.loaders.

First require queries package.preload[modname].(A table to store loaders for specific modules) If it has a value, this value (which should be a function) is the loader. Otherwise require searches for a Lua loader using the path stored in package.path. If that also fails, it searches for a C loader using the path stored in package.cpath. If that also fails, it tries an all-in-one loader (see package.loaders).

Once a loader is found, require calls the loader with a single argument, modname. If the loader returns any value, require assigns the returned value to package.loaded[modname]. If the loader returns no value and has not assigned any value to package.loaded[modname], then require assigns true to this entry. In any case, require returns the final value of package.loaded[modname].

If there is any error loading or running the module, or if it cannot find any loader for the module, then require signals an error.

require函数的调用形式为require "模块名"

加载给定的模块名modname,require函数首先会在表package.loaded中查看是否已经加载过,如果已经加载过,则会返回储存在package.loaded[modname]中的模块,否则,require函数将会尝试为此模块寻找一个加载器。

require函数将会被package.loader的数组引导来寻找适用于该模块的加载器,(package.loaders,是一个被require函数用来控制如何加载模块的表或者说数组)我们可以自己改变require函数寻找模块的方法,下面是package.loaders的默认配置的下的解释:

首先,require函数会查看package.preload[modname](用来储存特定模块加载器的表),如果有值(该值应是一个函数),则改值就是要找的加载器。否则,require函数将会通过储存在package.path中的路径来寻找一个Lua 加载器。如果也失败了,requier函数则会通过储存在package.cpath中的路径来寻找一个C 加载器。如果也失败了,require函数将会尝试使用一个package.loaders中的通用的加载器——all-in-one 加载器。

一旦找到加载器,require函数会传递一个单一的参数模块名modname到这个加载器,如果加载器有任何的返回值,require函数会将返回的值连同模块名modname会注册到表 package.loaded[modname]中。如果加载器没有返回值并且还没有任何值连同模块名modname会注册到表 package.loaded[modname]中,那么require函数将会注册 true 到该键值对的入口。任何情况下,require 函数都会返回表 package.loaded[modname]最终的值。

如果在加载或者运行该模块,亦或者完全没有找到该模块的加载器,则require函数会显示error。

将上方simpleMod.lua放置在当前目录的子目录Util下,调用代码:

--file: testMod.lua

print("Before the require function , packages in the package.loaded :")
for k in pairs(package.loaded) do print(k) end

print("package.preload loader number : "..#package.preload)

--lua5.1中的package.loaders 在lua5.2之后版本中更名为package.searchers
--所以使用package.loaders or package.searchers来兼容版本
for k,v in pairs(package.loaders or package.searchers) do print("loader : "..k .. "   "..tostring(v)) end

--require function ,PS: The Util Is A Subdirectory of current directory
local Mod = require "Util.simpleMod"

print("
After the require function , the table package.loaded")
for k in pairs(package.loaded) do print(k) end

--simpleMod's usage code
local mod = Mod:New("TestFeature")
mod:Update()

print("
ModFeature Name : "..mod.Name .. "
Version : "..mod._VERSION)

输出结果:

ModuleTest.PNG

module函数

简化module的创建

  • require会将模块名modname传递给loader,在loader加载模块时,我们可以在模块中接收传递的模块名

  • 有时我们会漏写创建模块最后的return语句,我们可以将所有与模块创建相关的设置任务都集中在开头,
    消除return语句的一种方法是,loader加载模块时将模块名modname以及模块注册至表package.loaded[modname]中

代码如下:

--file: simpleMod.lua

local _Mod = {}
_Mod._Mod = _Mod

local modname = ...
_G[modname] = _mod
package.loaded[modname] = _Mod

function _Mod:New(name)
    local mod = {}
    mod.Name = name or "Default Name"
    mod._VERSION = 0.01
    return setmetatable(mod , {__index = self})
end

function _Mod:Update()
    self._VERSION = self._VERSION + 0.01
    return self._VERSION
end

Lua5.1 & setfenv (f, table)

Sets the environment to be used by the given function. f can be a Lua function or a number that specifies the function at that stack level: Level 1 is the function calling setfenv. setfenv returns the given function.

As a special case, when f is 0 setfenv changes the environment of the running thread. In this case, setfenv returns no values.

当我们在创建模块或者访问同一个模块中的其它函数时,需要限定名称,就比如上面代码中的_Mod,为此,我们可以让模块的主程序块有一个独立的环境,这样不仅它的所有函数都可共享这个table,而且它的所有全局变量也都记录在这个table中。而模块所要做的就是将这个table赋予模块名和package.loaded。

这样,我们在调用同一个模块中的函数new时,也不用指定m了。在写自己的模块时,就省去了前缀;但是与此同时,当我们调用setfenv(1,m)函数之后,会将一个空table m作为环境,但是这样之后就无法访问前一个环境中全局变量了(例如setmetatablet之类的全局变量)

解决方法:
方法一:在调用setfenv(1,m)之前,为m设置元表,使元表的__index域指向_G,代码如下:

--file: simpleMod.lua

local _Mod = {}
_Mod._Mod = _Mod

local modname = ...
_G[modname] = _mod
package.loaded[modname] = _Mod

--Before calling the func setfenv() ,set the mestatable {__index = _G} to the _mod
setmetatable(_Mod,{__index = _G})
setfenv(1,_Mod)

function New(self,name)
    local mod = {}
    mod.Name = name or "Default Name"
    mod._VERSION = 0.01
    return setmetatable(mod , {__index = self})
end

function Update(self)
    self._VERSION = self._VERSION + 0.01
    return self._VERSION
end

方法二:在调用setfenv(1,m)之前,使用局部变量将全局变量_G保存起来,代码如下:

--file: simpleMod.lua

local _Mod = {}
_Mod._Mod = _Mod

local modname = ...
_G[modname] = _mod
package.loaded[modname] = _Mod

--Before calling the func setfenv() ,storage _G to the local variable
local _G = _G
setfenv(1,_Mod)

function New(self,name)
    local mod = {}
    mod.Name = name or "Default Name"
    mod._VERSION = 0.01
    --This way to use the variables in the _G table
    return _G.setmetatable(mod , {__index = self})
end

function Update(self)
    self._VERSION = self._VERSION + 0.01
    return self._VERSION
end

方法三:在调用setfenv(1,m)之前,只将需要使用的全局变量保存起来,代码如下:

--file: simpleMod.lua

local _Mod = {}
_Mod._Mod = _Mod

local modname = ...
_G[modname] = _mod
package.loaded[modname] = _Mod

--Before calling the func setfenv() ,storage useful variable the local variable
local setmetatable = setmetatable
setfenv(1,_Mod)

function New(self,name)
    local mod = {}
    mod.Name = name or "Default Name"
    mod._VERSION = 0.01
    --This way to use the variables in the _G table
    return setmetatable(mod , {__index = self})
end

function Update(self)
    self._VERSION = self._VERSION + 0.01
    return self._VERSION
end

Lua5.1 & module(...)

在Lua 5.1中,可以用module(...)的函数来代替以下代码:

-- local _Mod = {}
-- _Mod._Mod = _Mod

-- local modname = ...
-- _G[modname] = _mod
-- package.loaded[modname] = _Mod

-- setfenv(1,_Mod)

由于在默认情况下,module不提供外部访问,必须在调用它之前,为需要访问的外部函数或模块声明适当的局部变量。然后Lua提供了一种更为方便的实现方式,即在调用module函数时,多传入一个package.seeall的参数,相当于 setmetatable(_Mod, {__index = _G}):

module(...,package.seeall)

完整代码:

--file: simpleMod.lua

-- local _Mod = {}
-- _Mod._Mod = _Mod

-- local modname = ...
-- _G[modname] = _mod
-- package.loaded[modname] = _Mod

--Before calling the func setfenv() ,set the mestatable {__index = _G} to the _mod
-- setmetatable(_Mod,{__index = _G})
-- setfenv(1,_Mod)

module(...,package.seeall)

function New(self,name)
    local mod = {}
    mod.Name = name or "Default Name"
    mod._VERSION = 0.01
    --This way to use the variables in the _G table
    return setmetatable(mod , {__index = self})
    end

    function Update(self)
    self._VERSION = self._VERSION + 0.01
    return self._VERSION
end

Lua5.2之后

  • Function module is deprecated. It is easy to set up a module with regular Lua code. Modules are not expected to set global variables.

  • Functions setfenv and getfenv were removed, because of the changes in environments.

  • module函数被抛弃。用普通的Lua代码就可以很容易的创建模块。而模块也不需要去设置全局变量。

  • setfenv以及getfenv函数被移除,因为会对环境产生改变。

Lua5.2之后,如果require引入使用module声明和定义的模块就会报错

REF

http://lua-users.org/wiki/

http://www.lua.org/manual/5.1/manual.html#5.3

http://www.lua.org/manual/5.2/manual.html#pdf-package.searchers

https://www.runoob.com/manual/lua53doc/manual.html#pdf-require

http://www.jb51.net/article/55818.htm

https://moonbingbing.gitbooks.io/openresty-best-practices/lua/not_use_module.html

https://www.cnblogs.com/zsb517/p/6822870.html

原文地址:https://www.cnblogs.com/sylvan/p/8592472.html