Lua中模組以及實現方法指南_Lua

來源:互聯網
上載者:User

從使用的角度來看,一個模組就是一個程式庫,可以通過Lua自身提供的require來載入。然後便得到一個全域變數,表示一個table。這個table就是像一個名字空間,其內容就是模組匯出的所有東西,例如函數和常量。簡單的說,Lua中的模組就是一個table,table中可以包括任何東西。本文首先詳細介紹模組相關的require函數,包括該函數的執行流程以及尋找模組的路徑,然後介紹了實現模組的三種方法,並給出相應的優缺點。

 require函數

     該函數用來載入一個模組,即按指定的路徑和傳入的參數,尋找要載入的模組。函數原型如下:

      require (modname)

      該函數的執行流程如下:

      I、尋找表package.loaded,看modname是否已經載入過了。若是,則require函數直接返回package.loaded[modname],否則繼續執行,尋找模組的載入器(loader)。

      II、為了尋找載入器,require使用了數組package.searchers(Lua 5.2引入的,在之前的版本叫做package.loaders,實質兩者只是名字不同而已),數組中每個元素是一個函數。

     第一個函數用來是搜尋表package.preload,若存在,則返回相應的載入器。

     第二個函數是用來擷取Lua模組的載入器,其搜尋路徑儲存在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 

會用模組名來替換每個”?”,然後根據替換的結果來檢測是否存在這樣的一個檔案。這個工作是通過函數package.searchpath來做的。package.searchpath函數原型如下:
      package.searchpath (name, path [, sep [, rep]])

參數path是要尋找的字串,用分號隔開;name是要尋找的檔案;參數sep(預設值是”.”)可用於name中,在尋找過程中用rep(預設值是系統目錄的分隔字元)替換。比如path是

複製代碼 代碼如下:

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

要尋找foo.a,則會嘗試尋找檔案

複製代碼 代碼如下:

./foo/a.lua, ./foo/a.lc, 和/usr/local/foo/a/init.lua 

也就是說Lua支援具有層級性的模組名。
      第三個函數是用來擷取C模組的載入器,其搜尋路徑儲存在package.cpath中,它也是一個字串,比如:

複製代碼 代碼如下:

/usr/local/lib/lua/5.2/?.so;/usr/local/lib/lua/5.2/loadall.so;./?.so 

同樣會用模組名來替換每個”?”,然後根據替換的結果來檢測是否存在這樣的一個檔案。這個工作也是通過函數package.searchpath來做的。
     第四個函數是用all-in-one 載入器,使用這個功能,可以使得一個包裡麵包含多個C子模組。除了第一個外,其他三個除了返回載入器外,還會返回找到的檔案名稱作為額外的值。

      III、找到載入器後,require將用兩個參數調用這個載入器,一個是傳入的參數modname,另外一個是返回的額外值。如載入器返回一個不是nil的值,則把這個值賦值給package.loaded[modname]。如果載入返回返回一個nil並且載入器執行完後package.loaded[modname]還為空白,則把package.loaded[modname]賦值為true。不管那種情況,require都會返回package.loaded[modname]。如果在這個過程有任務錯誤,require函數就產生一個錯誤給調用者。

     最後關於require函數,值得注意的幾點是:

     I、如果require找到的是一個lua檔案,則通過loadfile來載入代碼,如果找到的是一個C程式庫,就通過loadlib來載入。注意,loadfile和loadlib都實質上載入代碼,並沒有運行他們。為了運行他們,require會用模組名作為參數來調用這些代碼。

     II、若要強制使require對同一庫載入兩次,可以簡單刪除package.loaded中的模組條目,即賦值相應的條目為nil。

     III、通過上面的載入過程分析知道,要載入自己的lua檔案或C庫,可以通過修改package.path或package.cpath的值,把要搜尋的路徑載入進去。

      IV、也可以定義自己的載入函數(除了已有的loadlib和loadfile等),比如載入ZIP檔案,甚至從web上下載一個檔案。

編寫模組的方法

     方法一:對於Lua5.0和5.1來說,編寫模組最簡單的方法是使用Lua自身提供的module函數(注意在Lua 5.2中被刪除了),比如要編寫一個模組foo,模組檔案foo_file.lua如下:

複製代碼 代碼如下:

module("foo", package.seeall) 
function test() 
end 

則在其他檔案要使用這個模組,方式如下:

複製代碼 代碼如下:

require(“foo_file.lua”) 
foo.test() 

並且執行require後,則會把模組foo就是全域環境的一個變數了,在其他地方也可以使用。module函數原型如下:

複製代碼 代碼如下:

module (name [, ···]) 

module在建立模組table之前,會先檢查package.loaded是否已包括了這個模組,或者是否已存在與模組同名的變數。如果由此找到了這個table,它就會複用這個table做為模組。也就是說,可以用module來開啟一個已建立的模組。

對於module函數來說,有以下問題,比如在模組檔案module0_test中有:

複製代碼 代碼如下:

module("mymodule", package.seeall)                                                                      
function foo() 
    print("Hello World!") 
end 

在另外一個檔案可以這樣使用這個模組:

複製代碼 代碼如下:

require "module0_test" 
mymodule.foo()             --Hello World! 
mymodule.print("example")  --example 

對於第二個調用不是報錯的,並且是非常奇怪的,這時因為module機制是在模組中找不的成員,則去_G全域變數找,實現方式類似如下:

do   local globaltbl = _G   local newenv = setmetatable({}, {     __index = function (t, k)       local v = t[k]       if v == nil then return globaltbl[k] end       return v     end,     __newindex = M,   })   if setfenv then     setfenv(1, newenv) -- for 5.1   else     _ENV = newenv -- for 5.2   end end 

在模組找不到的成員,則到_G中去尋找,並且這樣訪問也是非常低效的,因為要通過元表來訪問成員。

 方法二:該方法的基本思想是讓模組的主程式有一個獨佔的環境,這樣所有函數或變數都共用這個table,並且所有的全域變數都記錄在這個table中,當然局部變數是不會的。程式碼片段如下:

local modename = ... local M = {} _G[modename] = M package.loaded[modname] = M if setfenv then   setfenv(1, newenv) -- for 5.1 else   _ENV = newenv -- for 5.2 end 

如果這樣實現,在模組訪問_G中的變數時,需要加上首碼,比如_G.print。為瞭解決這個問題,有幾種方法,各有優缺點:

 I、設定M的元表,即setmetable(M, {__index = _G})這樣做後,訪問全域變數,都要通過元表,開銷比較大。

 II、設定local _G = _G,這樣做後,訪問全域變數仍然要加上首碼,但速度更快。

 III、把模組需要的全域變數都設定為局部變數,比如local io = io。這樣做會比較繁瑣,但是速度最快。

方法三:同樣是使用環境的概念。比如模組檔案如下:

複製代碼 代碼如下:

function foo()                                                                                          
    print("Hello World!")                                                                               
end 

為了使用它,方法如下:

local function Import(filename)                                       f = loadfile(filename)                                          local M = {}                                               setmetatable(M, {__index = _G})                                     setfenv(f,M)()   return M end  local FOO = Import("module2_test.lua")  FOO.foo() --output “Hello World!” 

用這種方法,只需調用Import方法,其傳回值就是模組,該方法把模組相關訪問工作,放在使用的模組的地方了。

以上所述就是本文的全部內容了,希望能夠對大家學習lua有所協助。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.