模組教學 |
|
實際上有兩個方法可以建立模組,供 5.1、5.2 使用的新方法,以及 5.0 和早期 5.1 使用的舊方法(現已停用)。我們將首先探討新方法,然後再討論 Lua 5.0 和 5.1 的舊方法。
建立範例檔案 mymodule.lua
,帶有以下內容
local mymodule = {} function mymodule.foo() print("Hello World!") end return mymodule
現在要在互動式直譯器中使用這個新模組,只要執行
> mymodule = require "mymodule" > mymodule.foo() Hello World!
在實際的腳本檔案中,建議將 mymodule
變數設定為局部變數
local mymodule = require "mymodule" mymodule.foo()
不過,由於我們在互動式直譯器中,所以在下一行會使它超出範圍,因此我們必須將它放入全域變數中。
這樣您就不必重新執行模組程式碼,便能在不同的檔案中載入同一個模組,Lua 會將模組快取在 package.loaded
表格中。為了示範這一點,假設我們將 mymodule.lua
中的 foo
變更為現在印出 "Hello Module!"
。如果我們只在與上述相同的階段繼續執行,就會發生下列情況
> mymodule = require "mymodule" > mymodule.foo() Hello World!
要實際重新載入模組,我們必須先將它從快取中移除
> package.loaded.mymodule = nil > mymodule = require "mymodule" > mymodule.foo() Hello Module!
另一個好處是,由於它們是儲存在變數中的普通表格,所以模組可以任意命名。假設我們認為在每次使用它的函式時,輸入 "mymodule"
太長
> m = require "mymodule" > m.foo() Hello Module!
有不同的方法可以將模組表格放在一起,您可以根據情況和個人偏好進行選擇
在頂端建立一個表格,並將您的函式新增到其中
local mymodule = {} local function private() print("in private function") end function mymodule.foo() print("Hello World!") end function mymodule.bar() private() mymodule.foo() -- need to prefix function call with module end return mymodule
讓所有函式都成為局部函式,並將它們放入尾端的表格中
local function private() print("in private function") end local function foo() print("Hello World!") end local function bar() private() foo() -- do not prefix function call with module end return { foo = foo, bar = bar, }
結合上述兩個範例
local mymodule = {} local function private() print("in private function") end local function foo() print("Hello World!") end mymodule.foo = foo local function bar() private() foo() end mymodule.bar = bar return mymodule
您甚至可以變更區塊的環境,以將建置的任何全域變數儲存在模組中
local print = print -- the new env will prevent you from seeing global variables local M = {} if setfenv then setfenv(1, M) -- for 5.1 else _ENV = M -- for 5.2 end local function private() print("in private function") end function foo() print("Hello World!") end function bar() private() foo() end return M
或者如果您不想將所有需要的全域變數儲存到局部變數中
local M = {} do local globaltbl = _G local newenv = setmetatable({}, { __index = function (t, k) local v = M[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 local function private() print("in private function") end function foo() print("Hello World!") end function bar() private() foo() end return M
請注意,這可能會使存取全域變數和模組變數的速度變慢一點,因為它使用 __index
函式。此外,使用空的「代理」表格,而不是針對模組提供 __index
元方法以及指向 _G
的 __newindex
元方法的原因在於會發生以下情況
> mymodule = require "mymodule" > mymodule.foo() Hello World! > mymodule.print("example") -- unwanted __index metamethod example
Lua 5.0 和 5.1 有個 module
函式,其用法如下
mymodule.lua
:
module("mymodule", package.seeall) function foo() -- create it as if it's a global function print("Hello World!") end
其使用方法如下
> require "mymodule" > mymodule.foo() Hello World!
其運作方式是為模組建立一個新表格,將其儲存在由 module
的第一個參數指定的全域變數中,並將其設定為區塊的環境,因此如果您建立一個全域變數,它就會儲存在模組表格中。
這將使得模組看不到全域變數(例如 print
)。一種解法是在呼叫模組之前將所有需要的標準函式儲存在區域變數中,但這很繁瑣,因此解法為 module
的第二個參數,這應該是呼叫以模組資料表作為參數的函式。package.seeall
給予模組一個带有指向全域資料表的 __index
的元資料表,因此模組現在可以使用全域變數了。這問題是模組使用者現在可以透過模組看到全域變數
> require "mymodule" > mymodule.foo() Hello World! > mymodule.print("example") example
這在最好的情況下很奇怪且出人意料,在最壞的情況可能是安全漏洞(如果你將模組交給受限的指令碼)。
module
被棄用的原因除了上述問題之外,還包括它強制將全域名稱加在模組使用者身上,以及在 5.2 中函式無法變更呼叫者的環境的事實(至少沒辦法在沒有除錯函式庫的情況下變更),這使得 module
無法實作。
5.0 及較早版本的 5.1 模組使用這個,但 5.2 和較新版本的 5.1 模組應該使用新方式(傳回一個資料表)。
如上所述,Lua 使用 package
函式庫來管理模組。
package.path
(針對以 Lua 撰寫的模組)和 package.cpath
(針對以 C 撰寫的模組)是 Lua 尋找模組的地方。它們是分號分隔的清單,而且每個條目都可以有 ?
,而這個 ?
將會以模組名稱替換。以下是它們範例
> =package.path ./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua > =package.cpath ./?.so;/usr/local/lib/lua/5.1/?.so;/usr/lib/x86_64-linux-gnu/lua/5.1/?.so;/usr/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so
package.loaded
是個資料表,已載入的模組會儲存在裡面,並以名稱作為索引。如先前所述,這像是個快取,讓模組不會被載入兩次,而是 require
會先從這個資料表取得模組,如果它是 false 或 nil,才會載入。
package.preload
是個函式資料表,其中與模組名稱有關聯。在搜尋檔案系統之前,require
會檢查 package.preload
是否有匹配的鍵。如果有,就會呼叫該函式,並將其結果作為 require
的傳回值。
其他欄位對一般使用 Lua 模組來說並不太重要,但如果你有興趣了解模組系統如何運作,手冊中有詳細說明:[1]