Lua中模塊和包

文章目錄


    通常,Lua語言不會設置規則。相反,Lua語言提供的是足夠強大的機制供不同的開發者實現最適合自己的規則。然而,這種方法對於模塊而言並不是特別適用。模塊系統的主要目標之一就是允許不同的人共享代碼,缺乏公共規則就無法實現這樣的分享。
    Lua語言從5.1版本開始爲模塊和包定義了一系列的規則。這些規則不需要從語言中引入額外的功能,程序猿可以使用目前爲止我們學習到的機制實現這些規則。程序員也可以自由地使用不同的策略。當然,不同的實現可能會導致程序不能使用外部模塊,或者模塊不能被外部程序使用。
    從用戶觀點來看,一個模塊就是一些代碼,這些代碼可以通過函數require加載,然後創建和返回一個表。這個表就像是某種命名空間,其中定義的內容是模塊中導出的東西,比如函數和常量。
    例如,所有的標準庫都是模塊。我們可以按照如下的方式使用數學庫:

local m = require "math"
print(m.sin(3.14))				-- 0.0015926529164868

獨立解釋器會使用跟如下代碼等價的方式提前加載所有標準庫:

math = require "math"
string = require "string"

這種提前加載使得我們可以不用費勁地編寫代碼來加載模塊math就可以直接使用函數math.sin。
    使用表來實現模塊的顯著優點之一是,讓我們可以像操作普通表那樣操作模塊,並且能利用Lua語言的所有功能實現額外的功能。在大多數語言中,模塊不是第一類值(即它們不能被保存在變量中,也不能被當作參數傳遞給函數等),所以那些語言需要爲模塊實現一套專門的機制。而在Lua語言中,我們則可以輕易地實現這些功能。
    例如,用戶調用模塊中的函數就有幾種方法。其中常見的方法是:

local mod = require "mod"
mod.foo()

用戶可以爲模塊設置一個局部名稱:

local m = require "mod"
m.foo()

也可以爲個別函數提供不同的名稱:

local m = require "mod"
local f = m.foo
f()

還可以只引入特定的函數:

local f = require "mod".foo 			-- (require("mod")).foo
f()

上述這些方法的好處是無須語言的特別支持,它們使用的都是語言已經提供的功能。

函數 require

    儘管函數require也只是一個沒什麼特殊之處的普通函數,但在Lua語言的模塊實現中扮演者核心角色。要加載模塊時,只需要簡單地調用這個函數,然後傳入模塊作爲參數。請記住,當函數的參數只有一個字符串常量時括號是可以省略的,而且一般在使用require時按照慣例也會省括號。不過儘管如此,下面這些用法也是正確的:

local m = require('math')
local modname = 'math'
local m = require(modname)

    函數require嘗試對模塊的定義做最小的假設。對於函數來說,一個模塊可以是定義了一些變量的代碼。典型地,這些代碼返回一個由模塊中函數組成的表。不過,由於這個動作是由模塊代碼而不是由函數require完成的,所以某些模塊可能會選擇返回其他的值或者甚至引發副作用。
    首先,函數require在表package.loaded中檢查模塊是否已被加載。如果模塊已經被加載,函數require就返回相應的值。因此,一旦一個模塊被加載過,後續的對於同一模塊的所有require調用都將返回同一個值,而不會再運行任何代碼。
    如果模塊尚未加載,那麼函數require則搜索具有指定模塊名的Lua文件(搜索路徑有變量package.path指定)。如果函數require找到了相應的文件,那麼就用函數loadfile將其進行加載,結果是一個我們稱之爲加載器的函數。
    如果函數require找不到指定模塊名的Lua文件,那麼它就搜索相應名稱的C標準庫。如果找到了一個C標準庫,則使用底層函數package.loadlib進行加載,這個底層函數會查找名爲luaopen_modname的函數。在這種情況下,加載函數就是loadlib的執行結果,也就是一個被表示爲Lua函數的C語言函數luaopen_modname。
    不管模塊是Lua文件還是C標準庫中找到的,函數require此時都具有了用於加載它的加載函數。爲了最終加載模塊,函數require帶着兩個參數調用加載函數:模塊名和加載函數所在文件名稱。如果加載函數有返回值,那麼函數require會返回這個值,然後將其保存在表package.loaded中,以便於將來在加載同一個模塊時返回相同的值。如果加載函數麼有返回值且表中的package.loaded【@rep{modname}]爲空,函數require就假設模塊的返回值是true。如果沒有這種補償,那麼後續調用函數require時將會重複加載模塊。
    要強制函數require加載同一模塊兩次,可以先將模塊從package.loaded中刪除:

package.loaded.modname = nil

下一次在加載這個模塊時,函數require就會重新加載模塊。
    對於函數require來說,一個常見的抱怨是它不能給待加載的模塊傳遞參數。例如,數學模塊可以對角度和弧度的選擇增加一個選項:

-- 錯誤的代碼
local math = require("math","degree")

這裏的問題在於,函數require的主要目的之一就是避免重複加載模塊,一旦一個模塊被加載,該模塊就會在後續所有調用require的程序部分被複用。這樣,不同參數的同名模塊之間就會產生衝突。

local mod = require "mod"
mod.init(0,0)

如果加載函數返回的是模塊本身,那麼還可以寫成:

local mod = require "mod".init(0,0)

請記住,模塊在任何情況下只加載一次;至於如何處理衝突的加載,取決於模塊自己。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章