Help:Lua
Lua是輕量的動態類型語言。MediaWiki的Scribunto擴展允許頁面調用Lua代碼,其中Lua代碼放置在「Module:」命名空間下。MediaWiki使用的Lua版本為5.1,但進行了一些修改,並移植了之後的版本的一些功能。
本幫助頁面將簡單地介紹Lua的語法、用法及特點等,但不會特別細緻,建議有一定編程基礎的用戶閱讀。請注意,本頁提及了一些MediaWiki及其擴展提供的庫,這些在MediaWiki之外的標準運行環境中是不直接具備的。本頁還會提及本wiki中的(包括搬運的)部分模塊,不一定適用於其他基於MediaWiki的網站。零基礎的用戶如需學習Lua語言,可在網上自行搜索入門教程。
變量[編輯原始碼]
Lua的變量分為全局變量和局部變量。局部變量應使用local關鍵字進行聲明。同其他語言一樣,我們使用等號進行賦值。在沒有聲明局部變量的情況下進行賦值,則默認賦值給全局變量。例如,
a = 1 -- 声明一个全局变量
do -- 一个作用域
local b = 1 -- 声明一个局部变量
b = "2" -- 修改一个局部变量
end
print(b) -- 局部变量超出作用域会失效,所以为nil
注意:
- 上面這個例子中,只是標準的開發環境,因此使用了全局變量,並使用print函數以將值輸出到控制台。MediaWiki不同於標準的開發環境,不應當修改全局變量,也不應該使用print函數。
- 在MediaWiki的模塊中,不應該創建或修改任何全局變量,而應該定義局部變量,並在模塊末尾使用return語句返回你需要返回的值。
局部變量也可以先聲明再賦值。沒有賦值的變量,以及不存在的變量,其值都為nil,使用這些變量並不會拋出錯誤,但仍需留意。
變量可以多重賦值,例如a, b = 1, 2
是有效的,但不支持a = b = 1
這樣的語法。等號右邊也可以是返回多個值的函數(如unpack或string.find)。如果等號右邊的值的個數超過左邊,則多餘的會被忽略;如果少於左邊的,則缺少的會賦值為nil。注意Lua沒有元組(tuple)。
變量名稱不能以數字開頭,不能為保留字,也不可以有中文。
數據類型[編輯原始碼]
Lua是動態類型語言,變量沒有類型,只有值有。聲明局部變量、函數參數等,均無需聲明類型。
基本類型介紹[編輯原始碼]
在Scribunto擴展中,Lua只有6個基本的類型:
nil[編輯原始碼]
空值(nil)表示這個值不存在。嘗試訪問不存在的變量或表中不存在的字段的值時,會得到nil。函數調用中未提供的參數的值都會被視為nil。很多函數也會在失敗時返回nil而不是拋出錯誤,例如tonumber函數在無法轉換成數字時會返回nil。
boolean[編輯原始碼]
布爾值(boolean)只有true或false。將其他數據類型轉化為布爾值時,只有false和nil視為false,其他的一律視為true。
string[編輯原始碼]
字符串(string)為一系列的8比特字節;這取決於應用程序以哪種特定的編碼來解析。字符串使用單引號或雙引號表示,單引號和雙引號無區別,也不會區分「字符」與「字符串」。字符串可以轉義。
字符串也可以使用長括號來定義。長括號之間可以夾雜着0個或更多個等號(兩邊的等號要等量)。例如,"abc"
、'abc'
、[[abc]]
、[==[abc]==]
都是等價的。注意:由長括號定義的字符串可以直接換行,且不會處理轉義序列。
Lua標準庫中的string庫可用於處理字符串,MediaWiki庫中的mw.text和mw.ustring則提供了更多的處理方法。
number[編輯原始碼]
Lua中的數字(number)均為雙精度浮點數,-9007199254740992到9007199254740992之間的整數都會準確表達,更大的數和帶有小數部分的數將會受到捨入的誤差。
數字可以用點(.)來表示小數,例如123456.78。數字也可以用不帶空格的科學計數法,例如1.23e-10、123.45e20或者1.23E5。也可以用16進制表示整數,方法就是以0x開頭,例如0x3A。
1/0會得到inf,0/0會得到nan。inf和nan都可以正確地儲存,但是並無直接的文字表示方法(無窮大可以使用math.huge
表示)。使用時,應當留意inf和nan可能帶來的潛在錯誤。nan不等於它自己,也就是說,nan == nan
會返回false。
Lua標準庫中的math庫可用於處理數字。
table[編輯原始碼]
表(table)是複合數據類型,是Lua中最複雜的一種數據類型,很像JavaScript中的對象(object)或者Python中的字典(dict)。在Lua中,表同時充當了映射、列表、集合的功能。
表由多個(或者零個)字段組成,每個字段都有鍵(key)和值(value),其中鍵可以是除nil或nan外的任何值。不存在的字段可視為其值為nil。鍵是唯一的,值不是唯一的。若鍵為表或函數,則判斷其唯一性是根據其是否為同一個對象。
聲明一個表的方法是:在一組花括號中,使用「[鍵] = 值」的方式表示一個字段,多個字段使用逗號或分號隔開。如果鍵是字符串的字面量,且該字符串符合變量名稱要求,則可以省略引號並使用「鍵 = 值」的方式表示。例如,{a = 1, b = "2"}
中,"a"
和"b"
都是鍵,1
和"2"
是對應的值,也可以表示為{["a"] = 1, ["b"] = 2}
。
聲明一個表時,鍵是可以省略的。例如,{"a", "b", "c", "d", "e"}
等價於{[1]="a", [2]="b",[3]="c", [4]="d", [5]="e"}
,這樣的表稱為數組(array)或序列(sequence),其特點就是鍵是連續不間斷的正整數。空表也可以視為數組
對於表t和數字b,如果滿足(t == 0 or t[b] ~= nil) and t[b + 1] == nil
,則稱b為表t的一個邊界(border)。數組只有一個邊界。有多個邊界的表不是嚴格的數組,稱為稀疏數組(sparse array),對其進行取長運算會返回其中一個邊界,存在不確定性。例如,{nil, 2, 3, nil, nil, 6}
是個稀疏數組,其邊界為0、3、6,因此其長度可能是0、3、6中的某一個值。
表也可以用作集合(set),如{a = true, b = true, c = true}
,因為鍵是不能重複的,所以可以通過判斷鍵對應的值是否存在來判斷集合是否包含某個元素。
表的字段訪問:可以通過「變量名稱[鍵]
」或「變量名稱.鍵
」(該方法的鍵必須是可作標識符的字符串)的方式來訪問表中的鍵對應的值,也就是說,找到一個鍵為指定的值(表、函數等需要是同一對象,字符串、數字等只需要值相等即可)的字段,並返回其值。例如,如果t是一個表,則t[true]
表示布爾值鍵true對應的值,t.abc
或t["abc"]
表示字符串鍵"abc"
對應的值。如果這個值不存在,返回nil。
表的字段賦值:可以通過賦值語句修改表中的鍵對應的值。可以給表添加字段、修改現有字段的值或者清除一個字段。例如,t[true] = "q"
表示將鍵true對應的值修改為字符串"q",t["abc"] = nil
或t.abc = nil
(該方法的鍵必須是可作標識符的字符串)表示將鍵"abc"
對應的值修改為nil(相當於刪除這個字段,如果其原來的值不是nil的話)。注意:鍵可以是除nil或nan外的任意值,可以是函數、表,甚至是表自身,也就是說t[print] = 1
、t[t] = t
這樣的用法都是有效的,而t[0/0] = 1
則是無效的(0/0相當於nan,參考#number)。
表不會自動複製。例如如果t是一個表,則賦值語句s = t
不會將t的內容複製到新的表s中,而是會讓變量s和t的指針都指向同一個內存空間,它們仍是同一個對象,對表s的修改和對t的修改是同步、等價的。
Lua中,判斷兩個表是否相等的標準是兩個表是否是同一個對象,而不是內容是否一致。例如,在上面這一段的例子中,s == t
就會返回true,而{} == {}
會返回false。一般來說,每對花括號都意味着創建了一個新的對象。
Lua標準庫中的table庫可用於處理表,其中大部分函數都是用於處理數組(表中的數組部分)的。此外,本wiki也搬運了元模塊TableTools,可用於對表進行一些高級的處理。
此外,表的索引、賦值、判斷相等等操作都可以受到#元表影響。
function[編輯原始碼]
Lua中的函數(function)是閉包,這意味着它們維護對它們聲明的作用域的引用,並可以訪問和操作該作用域中的變量。
定義一個函數的基本語法是:
function 函数名称(参数列表)
语句……
end
函數名稱可以是表的字段,例如t.abc。或者,你也可在function前面加上local
關鍵字,表示該函數是個局部變量。參數是可選的,且視為局部變量。定義函數時,可以使用...
表示不定數量的參數(參見select)。
函數內可以使用return語句,表示函數返回一個值,return語句後面應當緊跟代碼塊的結束。如果沒有執行return語句,函數不會返回值,可以認為返回了nil。
匿名函數的表示方法為:
function (参数列表)
语句……
end
匿名函數與普通的函數無區別,例如,下面兩個語句的效果是一樣的:
local function f(x) return x+1 end
local f = function (x) return x+1 end
注意:通過匿名函數賦值的方式定義的函數,如果需要在函數內調用函數自身,應當先聲明再賦值。例如:
- 正確:
local function f(x) return x < 5 and f(x + 1) or x end
- 錯誤:
local f = function (x) return x < 5 and f(x + 1) or x end
(這裡的f(x+1)中的f視為全局變量) - 正確:
local f; f = function (x) return x < 5 and f(x + 1) or x end
事實上,在Lua中,local function f(...)
是一個語法糖,在字節碼中,實際上是先聲明局部變量再賦值的。
調用函數的語法為:
函数名称(参数列表)
函數的調用可以單獨作為語句,也可以作為表達式。此外,如果調用函數時(無論作為語句還是表達式),只有一個參數,且該參數為字符串或表的字面量,則括號可以省略,例如:
print "abc"
等價於print("abc")
。print{1, 2, 3}
等價於print({1, 2, 3})
。
Lua的函數不支持方法重載或多重派發,也不能夠直接給參數設置默認值。調用函數時,缺失的參數視為nil[1],多餘的參數會被忽略。一種常見的間接設置默認值的方式是,檢查某個參數的值是否存在,如果為nil則賦值為默認值,例如:
function f(name)
name = name or 'default'
...
end
方法[編輯原始碼]
函數作為表的字段時可以作為方法(method)調用,會自動將表自身作為第一個參數。方法是面向對象的一種體現。
Lua並沒有嚴格意義上的方法,但Lua提供了下面兩種語法糖:
- 聲明方法:
function table:method(args)
等價於function table.method(self, args)
; - 調用方法:
table:method("foo")
等價於table.method(table, "foo")
。
此外,字符串也可以面向對象使用(參見#元表)。
類型轉換[編輯原始碼]
Lua的數據類型可以相互轉換。
tonumber函數可以將字符串轉化為數字,例如tonumber("123")
會返回123。不能轉化為數字的,則會轉化為nil。對字符串進行算數運算時會嘗試自動轉換成數字。
tostring函數可以將任意值轉化為字符串。表會轉化為"table"
(受#元表影響的除外),函數會轉化為"function"
。數字與字符串連接、數字之間連接時會自動轉換成字符串。
type函數可以返回值的數據類型,用字符串表示。例如type(1/0)
會返回"string"
,type(true)
會返回"boolean"
。
運算[編輯原始碼]
和大多數語言一樣,Lua支持算術運算。其中,除以零不會出錯,會得到inf或nan。模運算的定義為a % b == a - math.floor( a / b ) * b
,例如-4%3會返回2而非-1。冪運算的符號為^
。對字符串進行算數運算,會嘗試使用tonumber函數轉化為數字,如無法轉換則會拋出錯誤。
關係運算包括==
、~=
、<
、>
、<=
和>=
,運算結果一定是布爾值。對於==
,不同類型的數據比較為false。注意表或函數的比較會比較指針,當兩個值指向同一個表或函數時才會返回true,除非受元表影響。
邏輯運算包括and
、or
和not
。注意:邏輯運算的結果不一定是布爾值,且進行邏輯運算時,並不會提前把所有值算出來,例如3 or error()
的值為3,不會拋出錯誤。可以使用邏輯運算來組成「三元運算」,例如a and b or c
相當於JavaScript、Julia或Java中的a ? b : c
或Python中的b if a else c
,前提是b不能為false。
對兩個字符串進行連接運算的方式是a .. b
,而非a + b
或a * b
。可以對數字進行連接,連接時會嘗試轉化為字符串。注意:Lua的字符串是不可變的(immutable),而且Lua不提供任何類型的「字符串構造器(string builder)」,所以反覆進行a = a .. b
會多次創建新的字符串,並最終將舊字符串作為垃圾收集。如果許多字符串都需要連接,則應使用string.format,或將所有的字符串添加到一個數組然後最後使用table.concat()連接。
#a
可以獲取字符串或者數組a的長度。對於字符串,會獲取其字節數,類似於string.len,獲取字符數可以使用函數mw.ustring.len。對於表,只會獲取其數組部分的長度,其他部分的字段一律忽略(對於{1, 2, nil, 4}
這樣的稀疏數組,其長度是其邊界中的某一個,例如可能為2或4[2]),如需獲取表中所有字段的個數,應使用一次for循環,或導入模塊:TableTools並使用其中的size函數。
對於表,上述所有運算都可以通過#元表控制。
優先級[編輯原始碼]
Lua中,各運算的優先級如下:
^
not
#
-
(負號)*
/
%
+
-
(減號)..
<
>
<=
>=
~=
==
and
or
此外,對於同級運算,a/b/c
相當於(a/b)/c
,a^b^c
相當於a^(b^c)
。
流程控制[編輯原始碼]
Lua和大多數語言一樣,具有條件語句和循環語句,沒有switch——case語句。
條件語句[編輯原始碼]
條件語句的基本語法是:
if 条件1 then
语句块1
elseif 条件2 then
语句块2
else
语句块3
end
當條件1成立時,執行語句塊1,否則當條件2成立時,執行語句塊2,否則執行語句塊3。elseif和else的結構都是可選的,當然elseif可以有多個。
循環語句[編輯原始碼]
while循環的基本語法:
while 条件 do
语句块
end
repeat循環的基本語法:
repeat
语句块
until 条件
在repeat循環中,語句塊至少執行一次。條件可以使用語句塊中的局部變量。
for循環的基本語法:
for 名称 = 表达式1, 表达式2, 表达式3 do
语句块
end
這種循環會聲明一個局部變量,由「表達式1」的值開始,以「表達式3」的值為步長,循環到「表達式2」的值。表達式3可以省略,默認為1,但不能為false或nil。
for語句除了用來循環之外,還可以進行迭代。語法為:
for 变量 in 表达式 do
语句块
end
變量、表達式可以有多個。通常表達式是返回3個值的一個函數調用。如果迭代器函數可以被寫入,那麼只取決於傳入的參數,這會更加有效。否則,在Lua中編程建議返回閉包比返回表更好,因為靜態變量在每次迭代更新其成員。
在上述for、repeat和兩種for循環中,均可使用break
關鍵字來立即跳出循環,break後面內容不再執行。Lua沒有continue語句[3],但可以調用包含循環代碼塊的函數,用return
來代替。
常用的迭代函數有pairs、ipairs、string.gmatch、mw.text.gsplit等。例如,對於一個普通的表t,我們可以使用這樣的方式迭代其所有的鍵和值:
for k,v in pairs(t) do
含有k和v的语句块
end
元表[編輯原始碼]
每個表都可以設置一個元表(metatable),用來修改特定的行為。setmetatable(t, m)
可以用來將表t的元表設為m(m也可以為nil,表示取消元表設置)[4],該函數自身也會返回修改後的表;getmetatable(t)
可以用來獲取表t的元表。
如果元表具有__index字段,則當通過指定的鍵訪問表的字段的時候,如果該字段不存在,則會使用元表__index字段。如果__index是表,則會訪問這個表的字段。如果__index是函數,則會調用這個函數。例如,訪問t[k]
時,如果表本身的這個字段不存在,則會嘗試訪問__index[k]
(如果__index是表)或__index(t, k)
(如果__index是函數)。rawget函數可繞過這個元方法。
__newindex字段則用於修改給表的字段賦值的行為。賦值給表的一個不存在的鍵時,如果__newindex字段的值是表,則會在該表中重複賦值。如果__newindex的值是函數,則會調用這個函數。例如,執行t[k] = v
時,表本身不存在這個字段,則會執行__newindex[k] = v
(如果__newindex是表)或__newindex(t, k, v)
(如果__newindex是函數)。rawset函數可繞過這個元方法。
__call字段可以讓表能像函數那樣調用,值必須是函數。如果這個元表字段存在,則調用表t(...)
時,會像__call(t, ...)
這樣調用,而不是拋出無法調用表的錯誤。
__metatable字段用於保護元表,防止其被訪問或修改。如果該字段存在,當對表進行getmetatable時,不會返回真正的元表,而是返回該字段的值[5],如果進行setmetatable,則會拋出錯誤[4]。
__mode字段用於使表保持弱引用。在Lua中,變量和表的字段對對象(函數、表等)的引用是強引用,只要某個函數和表依然存在引用,例如存儲在某個全局變量或者沒有超出作用域的局部變量中,或作為鍵或者值存儲在未被清理掉的表中,那麼它就不會被垃圾收集機制清理掉,而是一直存儲在內存中。弱引用則表示其引用不會阻止該表中存儲的內容被清理。當__mode
的值為k
表示鍵保持弱引用,v
表示值為弱引用,kv
表示鍵和值均為弱引用。例如:
t = setmetatable({value = {1, 2, 3}}, {__mode = 'v'})
print(t.value)
collectgarbage()
print(t.value)
輸出的結果為:
table nil
元表的一些其他字段用法如下,其值一般都是函數,函數的第一個參數是表自身。例如,如果元表__add
字段存在,則對表t計算t + a時,則會調用__add(t, a)
,而不是拋出無法對表進行算術運算的錯誤。
字段名稱和用法 | 對應操作 |
---|---|
__add(self, v) | self + v |
__sub(self, v) | self - v |
__mul(self, v) | self * v |
__div(self, v) | self / v |
__mod(self, v) | self % v |
__pow(self, v) | self ^ v |
__unm(self) | -self |
__concat(self, v) | self .. v |
__len(self) | #self |
__eq(self, v) | self == v |
__lt(self, v) | self < v |
__le(self, v) | self <= v |
__pairs(self)[6] | pairs(self) |
__ipairs(self)[6] | ipairs(self) |
__tostring(self) | tostring(self) |
所有字符串共用同一個元表,該元表的__index字段等同於string庫的一個副本,以使得字符串能夠作為對象使用。比如,string.match("label123", "^label%d")
等價於("label123"):match("^label%d")
。注意這個元表無法通過getmetatable函數獲得[7]。
注釋[編輯原始碼]
- ↑ 如果函數有不定數量的參數,則這些參數的數量是準確的;此外一些Lua內置函數也不會補充參數,例如
tostring()
會拋出錯誤,而不是理解為tostring(nil)
。 - ↑ 需要留意這種情況,因為會導致一些不確定性。
- ↑ Lua 5.2以上的版本具有標籤功能,可達到類似於continue的效果,但MediaWiki使用的Lua 5.1,是不支持標籤的。
- ↑ 4.0 4.1 原版Lua中,debug.setmetatable函數可以強制設置一個值(不限於表)的元表,且不受__metatable元方法影響。MediaWiki刪除了此函數。
- ↑ 原版Lua的debug.getmetatable函數可以強制獲取任意值的元表,不受__metatable元方法影響。MediaWiki刪除了此函數。
- ↑ 6.0 6.1 原版Lua 5.1沒有__pairs和__ipairs的功能(是在後面的版本才加入的),不過Scribunto擴展在Lua中移植了該功能,因此你可以在MediaWiki中使用此元表。
- ↑ 原版Lua可以獲取任意值的元表,只要元表存在。