模組教學

lua-users home
wiki

建立和使用模組

實際上有兩個方法可以建立模組,供 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 模組應該使用新方式(傳回一個資料表)。

package 資料表

如上所述,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]

另請參閱


RecentChanges · preferences
edit · history
最後編輯於 2018 年 2 月 26 日凌晨 12:34 GMT (diff)