從用戶的觀點來看,一個模塊就是一個程序庫,可以通過require來加載。然后得到了一個全局變量,表示一個table。
這個table就像一個名稱空間,其內容就是模塊中導出的所有東西,比如函數和變量。一個規范的模塊還應該使得require返回這個table。
顯然,在Lua中,模塊也是"第一類值"。
比如,用戶需要調用一個模塊中的函數,最簡單的方法:
require "mod"
mod.foo
如果希望能使用較短的模塊名稱,則可以為模塊設置一個全局名稱:
local m = require "mod"
m.foo()
還可以為個別函數設置不同的名稱:
require "mod"
local f = mod.foo
f()
require 函數細節
require函數加載模塊時,會檢查package.loaded是否已經加載。所以一個模塊只會加載一次。
如果require為指定模塊找到了一個Lua文件,它就會通過loadfile來加載該文件。如果找到的是一個C程序庫,就通過loadlib來加載。請注意loadfile和loadlib都只是加載了代碼,並沒有運行它們。為了運行代碼,require會以模塊名作為參數來調用這些代碼。
require用於搜索Lua文件的路徑放在常量package.path中。當Lua啟動后,便以環境變量LUA_PATH的值來初始化這個變量。如果沒有找到這個環境變量,則使用一個編譯時定義的默認路徑來初始化。
如果require無法找到和模塊相符的Lua文件,就會尋找C程序庫。這類搜索是從變量package.cpath來獲取路徑。
模塊的創建方法
在Lua中創建一個模塊的最簡單方法是:創建一個table,並將所有需要導出的函數放入其中,最后返回這個table。
比如:
1 complex = {} 2 3 function complex.new(r, i) return {r=r, i=i} end 4 5 -- 定義一常量 6 complex.i = complex.new(0,1) 7 8 function complex.add(c1,c2) 9 return complex.new(c1.r + c2.r, c1.i + c2.i) 10 end 11 12 function complex.sub(c1, c2) 13 return complex.new(c1.r - c2.r, c1.i - c2.i) 14 end 15 16 .... 17 18 return complex
在這里,必須顯示地將模塊名放到每個函數的定義中。而且在函數定義中,一個函數調用另外一個函數,必須限定使用被調用函數的名稱。
但是,我們是沒有必要寫模塊名字的,因為require會將模塊名字作為參數傳遞給模塊。
1 local modname = ... 2 local M = {} 3 _G[modname] = M 4 package.loaded[modname] = M 5 6 M.i = {r = 0, i = 1} 7 8 function M.new(r, i) 9 return {r=r, i=i} 10 end 11 12 function M.add(c1, c2) 13 return M.new(c1.r + c2.r, c1.i + c2.i) 14 end 15 16 ...
另外一個改進是不需要return 模塊名字了。因為,如果一個模塊沒有返回值的話,require就會返回package.loaded[modname]的當前值。
現在的創建模塊的基本方法缺點在於:在訪問同一模塊的其他方法時候,必須限定名稱。通過使用"函數環境"這種技術,可以解決這個問題。
1 local modname = ... 2 local M = {} 3 _G[modname] = M 4 package.loaded[modname] = M 5 setfenv(1, M) 6 7 function M.new(r, i) return {r=r, i=i} end 8 9 function add(c1, c2) 10 return new(c1.r + c2.r, c1.i + c2.i) 11 end 12 13 ...
這里add函數就從環境中得到new也就是complex.new。但是由於使用了環境,會導致訪問其他模塊出現問題。比如訪問全局模塊_G。
一般較好的解決辦法是將需要用到的模塊聲明為局部變量:
1 local modname = ... 2 local M = {} 3 _G[modname] = M 4 package.loaded[modname] = M 5 6 -- 導入 7 local sqrt = math.sqrt 8 local io = io 9 10 setfenv(1, M)
在Lua5.1后,Lua提供了一個新函數modle,包括了:
1 local modname = ... 2 3 local M = {} 4 5 _G[modname] = M 6 7 package.loaded[modname] = M 8 9 setfenv(1, M)
這些功能。默認情況下,module不提供對外訪問。必須在調用它之前,為所需要訪問的外部函數或者模塊聲明恰當的局部變量。也可以通過繼承來實現外部訪問。只需要在module時候加一個選項package.seeall。這個選項等價於以下代碼:
setmetatable(M, {__index = _G}),只需要這么做:module(..., package.seeall)