Lua中的模块和使用
簡介
從Lua5.1版本開始,就對模塊/包添加了新的支持,可是使用require函數(shù)和package函數(shù)來加載模塊,使用table模擬module來定義模塊。
函數(shù)require用于加載模塊,module用于創(chuàng)建模塊。
傳統(tǒng)模式下的模塊機制module
1.什么是module
對開發(fā)來說,使用module可以有效分隔代碼,實現(xiàn)代碼共享,便于代碼管理。
對于用戶來說,一個module相當于一個Xnix中的共享庫so或者windows中的動態(tài)庫dll。
2.如何編寫module
lua是通過table來實現(xiàn)模塊的,典型的寫法如下。
定義一個lua模塊文件 – moduleA.lua
-- moduleA.lua -- 通常是加local的,加了local是局部變量,需要return一下。 -- 如果不加,則M默認注冊到_G中,require后,即使不return也可以直接使用M。local M = {} -- 通過table來實現(xiàn)模塊M.work = function(...)print("function working")for i, v in ipairs{...} doprint(i, v)end-- do some job. endreturn M這里定義了一個具有變長參數(shù)的函數(shù)–其函數(shù)參數(shù)個數(shù)可變。參數(shù)的定義形式為...,在內(nèi)部訪問時,也使用{...},和使用table的方式一樣。
3.使用module
要使用定義好的module,使用require函數(shù),加載定義的module,然后就可以使用。
-- test.lualocal m = require "moduleA"m.work('a','b',1,2,3)module加載位置
要加載一個模塊,就必須的知道這個模塊在哪里。知道了這個模塊在哪里以后,才能進行正確的加載。當我們寫下require “mod”這樣的代碼以后,Lua是如何找這個mod的呢?
在搜索一個文件時,在windows上,很多都是根據(jù)windows的環(huán)境變量path來搜索,而require所使用的路徑與傳統(tǒng)的路徑不同,require采用的路徑是一連串的模式,其中每項都是一種將模塊名轉(zhuǎn)換為文件名的方式。require會用模塊名來替換每個“?”,然后根據(jù)替換的結(jié)果來檢查是否存在這樣一個文件,如果不存在,就會嘗試下一項。路徑中的每一項都是以分號隔開。而且lua模塊包括lua模塊,以及c實現(xiàn)的模塊。其對于require用于搜索的Lua文件的路徑存放在變量package.path和package.cpath中。我們可以在輸出看看。
檢查一下lua系統(tǒng)中的環(huán)境,執(zhí)行命令:
$ lua Lua 5.2.4 Copyright (C) 1994-2015 Lua.org, PUC-Rio stdin:1: unexpected symbol near char(228) > print(package.path) /usr/local/share/lua/5.2/?.lua;/usr/local/share/lua/5.2/?/init.lua;/usr/local/lib/lua/5.2/?.lua;/usr/local/lib/lua/5.2/?/init.lua;./?.lua > print(package.cpath) /usr/local/lib/lua/5.2/?.so;/usr/local/lib/lua/5.2/loadall.so;./?.so可以再看看各種函數(shù)和對象的類型
> print(print) function: 0x104cb304d > print(require) function: 0x7fdc3b403db0 > print(package) table: 0x7fdc3b403740 > print(_G) table: 0x7fdc3b4028d0 > print(module) function: 0x7fdc3b403d60 >require機制
1.require實現(xiàn)原理:
function require(name)if not packge.loaded[name] then ---- 避免重復加載local loader = findloader(name) ---- 如果是so,就以`loadlib`方式加載文件,如果是lua文件,就以`loadfile`方式加載文件。if loader == nil thenerror("unable to load module " .. name)endpackage.loaded[name] = true -- 將模塊標記為以加載,我們有時候會看到require返回true的現(xiàn)象,是由于被調(diào)用的模塊,沒有顯示的執(zhí)行package.loaded[modname] = M-- 或者給出return M這樣的返回值。local res = loader(name) -- require會以name作為入?yún)韴?zhí)行該文件,如果有返回結(jié)果,就將返回結(jié)果保存在package.loaded[name]中,-- 如果沒有返回結(jié)果,就直接返回package.loaded[name]。如果我們在被調(diào)用的文件中直接寫明return 1。-- 則調(diào)用者的require的返回結(jié)果就是1。但是只要我們顯示的在require文件中寫明了_G[modname] = M,-- 我們?nèi)匀豢梢栽趓equire之后,直接使用M作為名字來調(diào)用,是由于將M加入到了_G中。if res ~= nil thenpackage.loaded[name] = resendendreturn package.loaded[name] end2.require解析:
傳參: require會將模塊名作為參數(shù)傳遞給模塊
返回值:如果一個模塊沒有返回值的話,require就會返回package.loaded[modulename]作為返回值。
3. package.loaded是系統(tǒng)使用的table
require會將返回值存儲到package.loaded–一個table– 中;如果加載器沒有返回值,require就會返回table package.loaded中的值。可以看到,我們上面的代碼中,模塊沒有返回值,而是直接將模塊名賦值給table package.loaded了。這說明package.loaded這個table中保存了已經(jīng)加載的所有模塊。
現(xiàn)在我們就可以看看require到底是如何加載的呢?
1.先判斷package.loaded這個table中有沒有對應(yīng)模塊的信息;
2.如果有,就直接返回對應(yīng)的模塊,不再進行第二次加載;
3.如果沒有,就加載,返回加載后的模塊。
環(huán)境
lua用_G一張表保存了全局數(shù)據(jù)(變量,函數(shù)和表等)。
我們在lua中定義一個module,如果不加local,則它是一個注冊在全局下的表。我們通過加local避免了它在污染全局表空間,只在本文件生效。如果我們沒有將其注冊到_G下,在其他文件是無法直接通過他的原始名字來訪問的。這里有一個不便利的地方,每個函數(shù)前面都要帶M,M的下的函數(shù)相互訪問也要帶M頭。
解決方法:通過setfenv
local modname = ...local M = {}_G[modname] = Mpackage.loaded[modname] = Msetfenv(1, M)后續(xù)的函數(shù)直接定義名字,因為他們的環(huán)境空間已經(jīng)由_G改為了M。
如果要使用全局函數(shù),則可以本地額外增加一條local _G = _G或者setmetatable(M, {__index = G})。
更好的方法是在setfenv之前將需要的函數(shù)都保存起來,local sqrt = math.sqrt
定義module – 使用module函數(shù)
在定義一個模塊時,前面的幾句代碼都是一樣的,就分為以下幾步:
1.從require傳入的參數(shù)中獲取模塊名;
2.建立一個空table;
3.在全局環(huán)境_G中添加模塊名對應(yīng)的字段,將空table賦值給這個字段;
4.在已經(jīng)加載table中設(shè)置該模塊;
5.設(shè)置環(huán)境變量。
就是這幾步,在每一個模塊的定義之前都需要加上,有點麻煩,在Lua5.1中提供了一個新函數(shù)module,它替代我們完成以上這些步驟完成的功能。
在編寫一個模塊時,可以直接用以下代碼來取代前面的設(shè)置代碼:
local moduleName = ...local M = {} -- 局部的變量 _G[moduleName] = M -- 將這個局部變量最終賦值給模塊名package.loaded[moduleName] = Mlocal sqrt = math.sqrt -- 在我們自己的模塊中需要用到math.sqrt這個函數(shù),所以就先保存下來 local io = io -- 需要用到io庫,也保存下來 setfenv(1, M) -- 設(shè)置完成以后,就不能再使用_G table中的內(nèi)容了等同于
module(modname)。默認的情況下,module不提供外部的訪問的,也就是說,你無法訪問前一個環(huán)境了。如果要訪問外部變量,兩種方法:
1.在聲明module之前,local 變量 = 外部變量
2.使用module(modname, package.seeall), 等價于setmetatable(M, __index = _G)
在使用module時是這樣解決的:
module(..., package.seeall)其功能就好比之前的功能再加上了setmetatable(M, {__index = _G})。有了這一句代碼,基本上就可以說萬事不愁了。
使用module函數(shù)實現(xiàn)模塊化
網(wǎng)上一個小示例:
先定義一個module:
mypack.lua
--mypack.lua module(..., package.seeall) --定義module ver = "0.1 alpha" function aFunInMyPack()print("Hello!") end _G.aFuncFromMyPack = aFunInMyPack測試代碼test.lua:
--testP.lua: pack = require "mypack" --導入module print(ver or "No ver defined!") print(pack.ver) print(aFunInMyPack or "No aFunInMyPack defined!") pack.aFunInMyPack() print(aFuncFromMyPack or "No aFuncFromMyPack defined!") aFuncFromMyPack()執(zhí)行結(jié)果:
$lua test.lua No ver defined! 0.1 alpha No aFunInMyPack defined! Hello! function: 0x7fe1e1501390 Hello!目錄層次
如果要更好地組織代碼,可以把不同的模塊放到不同的目錄層次。
比如
在工作執(zhí)行目錄下,包括了test.lua,以及sub目錄,其下放置了mypack.lua – 對應(yīng)mypack模塊。
則test.lua中require時,需要包括模塊目錄形式,require "sub.mypack"
注意這里使用的是.,而不是\。
總結(jié)
以上是生活随笔為你收集整理的Lua中的模块和使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python logging之multi
- 下一篇: DC粉丝有福了!DC宣布多部新片项目 你