標籤:
Lua提供了一個名為require的高層函數來載入模組,但這個函數只假設了關於模組的基本概念。
對於require而言,一個模組就是一段定義了一些值(函數或者包含函數的table)的代碼。
為了載入一個模組,只需要簡單調用require "modname" 。這個函數首先會尋找package.loaded表,檢查modname是否載入過。
如果載入過,require返回package.loaded[modname]中的值。
否則,它試著為模組尋找“載入器”。
首先require會搜尋Lua file 。如果找到lua檔案,就調用loadfile載入,它的傳回值是函數 即"載入器"。
如果找不到Lua檔案,會尋找C庫,如果找到,就調用package.loadlib載入,去尋找luaopen_modname標誌函數。
每次找到載入器,require就用兩個參數調用載入器:modname和一個在擷取載入器過程中得到的參數(如果通過尋找檔案得到的載入器,這個額外參數就是檔案名稱)。
如果載入器返回非空值,require將這個值賦給package.loaded[modname].否則也會為其設定true值,無論什麼情況,require都會返回package.loaded[modname]的最終值。如果在載入或運行時有錯誤,或是無法為模組找到載入器,require都會拋出錯誤。
如果要用require載入模組兩次,我們可以把package.loaded[modname]給擦除掉。
packag.loaded.[modname] = nil
有時需要講一個模組改名,以避免名字衝突。比如同一個模組有不同版本的情況。對於一個lua檔案來說,改掉名字就可以了。
但是對於一個C程式庫,我們就不能編輯其中的luaopen_*函數的名稱了。為此,require用到了一個小技巧:
如果一個模組中包含了連字號 " - " ,require就會用連字號後的內容來建立luaopen_*函數名。
比如一個模組名為module-hello,require就會認為它的open函數是luaopen_hello,並不是luaopen_a-b。
因為在C文法中,連字號作為函數名是非法的。
因此,如果我們需要用兩個同名的mod模組,可以把其中一個重新命名為mod-hello,一個命名為mod-world。
當調用
m1 = require "mod-hello"
require函數會找到這個mod-hello檔案,並且在這個檔案裡,去找luaopen_mod函數。
注意:Lua5.2與Lua5.3這裡是完全相反:5.2移除"-"之前的,5.3移除“-”之後的。
Lua5.3的版本,如果遇到a.b.c-v2.1的模組名,函數名就是luaopen_a_b_c。橫線後的都忽略掉。
路徑搜尋
當搜尋一個Lua檔案的時候,rquire用的的路徑是一系列樣板(templates),其中的每一個指定了模組的檔案名稱。
在每個路徑樣板中都包含了一個可選的‘?‘符號,require會用模組名去替代這個問號(如果有的話),再去檢查這個檔案是否存在。
如果該樣板沒有,就從下一個樣板繼續尋找。這些路徑樣板被";"一個個隔開:
?;?.lua;c:\Windows\?;/usr/local/lua/?/?.lua
require "mod" 語句會尋找下面的檔案:
modmod.luac:\Windows\mod/usr/loacl/lua/mod/mod.lua
require只處理 ";" 和 "?" 符號,其他符合則是路徑自己定義的。
require找lua檔案時路徑變數是package.path。Lua一開始會用LUA_PATH_5_3環境變數去初始化它。
如果沒有定義這個環境變數,就會用LUA_PATH。如果兩個都沒有定義,Lua會用luaconf.h裡的預設路徑。
環境變數中出現的所有“;;”都會被替換成預設路徑。
比如你把LUA_PATH_5_3設定為“mydir/?.lua;;”,最後的路徑會是"mydir/?.lua"之後跟著的是預設路徑。
require尋找C庫的方法也是一樣的,但是變數變成了package.cpath。初始化的時候使用的是LUA_CPATH_5_3和LUA_CPATH。
在UNIX中是這樣的:
./?.so;/usr/local/lib/lua/5.3/?.so
package.searchpath函數包含了所有搜尋庫的規則,它接收一個模組名和一個路徑。
路徑是一系列以分號分隔的模板構成的字串,在其中搜尋指定的name。
返回第一個可以用讀模式開啟(並馬上關閉該檔案)的檔案的名字。如果不存在這樣的檔案,即返回nil加上錯誤訊息。
該錯誤訊息列出所有嘗試開啟的檔案名稱。
搜尋器
用於require控制如何載入模組的表--package.searchers
表內的每一項都是一個尋找器函數,當尋找一個模組時,require按照次序調用這些尋找器,並傳入唯一的模組名參數。
成功則返回另一個函數(模組的載入器)和一個載入器需要的參數。nil則表示錯誤。
Lua用四個尋找器函數初始化該表:
1> 在package.preload表中尋找載入器,沒有傳回值。
2> 尋找Lua庫的載入庫,用儲存在package.path中的路徑來尋找
3> 尋找C庫的載入庫,使用儲存在package.cpath中的路徑。
4> 一體化載入器,從C路徑中尋找指定模組的根名字。例如“a.b.c”;它將尋找a這個庫。如果找到就會在裡面找子模組的載入函數,就是找luaopen_a_b_c。
比如尋找 “hello.core” 模組,就是尋找hello庫的core子模組。對應的函數就是luaopen_hello_core。
這樣可以把若干C子模組打包進單個庫。每個子模組都可以有原本的載入函數名。
以上內容來自:《Lua程式設計第二版》和《Programming in Lua third edition 》
Chapter 15_1 require函數