同步發表於個人環境
Lua並沒有完整的模組系統,更多的是依賴模組開發者的設計。在Lua 5.1曾經有module()
的函數可用,但於Lua 5.2已經被移除。更多的需要使用_G
、_ENV
來使用,相關說明可以參考全局表與環境表。
module (name [, ...])
在require()
之前,需要先說一下如何載入程式檔案並執行。
對於模組化設計,最重要的一點是如何切割程式為不同檔案,接著就是如何串連不同檔案。在之前介紹過load
,Lua提供了另一個類似的方法loadfile
。
首先先建立個 hello.lua 檔案,簡單就好:
print "Hello, World"
然後才是主程式 loadfile_hello_example.lua :
hello = loadfile("./hello.lua")
hello() --> Output: Hello, World
如果你分別於REPL環境打入這兩行指令,你會發現第一行指令並沒有任何輸出結果,只得到一個hello函數。只有在第二行執行函式時,才真正執行hello.lua程式檔案的內容。
要執行一個檔案,有更直接的方法:
dofile("./hello.lua") --> Output: Hello, World
這一行就相等於上面兩行~
如果你寫過C/C++的模組,應該使用過#ifndef
,來處理同一個檔案多次載入的問題。Lua雖然模組系統是由模組開發者可以客自化的,但提供一個基礎、簡單的require()
函數,這個函數會紀錄已經載入過得檔案、模組於package.loaded
hello = require "hello" --> Output: Hello, World
for k, v in pairs(package.loaded) do
print(k, v)
end
package table: 0x5608ff174710
table table: 0x5608ff174cf0
io table: 0x5608ff1752e0
os table: 0x5608ff175b70
string table: 0x5608ff176760
math table: 0x5608ff176fb0
hello true
debug table: 0x5608ff178730
_G table: 0x5608ff172c00
utf8 table: 0x5608ff178370
coroutine table: 0x5608ff174d60
在package.loaded
可以發現已經有了hello
這個欄位。另外,可以注意到的是,我們當前的寫法使得其值是true
,而其他的是table
。通常有一些慣例,最後會提到。
喔對,如果再require
一次,並不會多一次執行,會直接回傳true
hello = require "hello" --> nothing
print(hello) --> true
你可以注意到,require
後面接的參數路徑,並不需要加上副檔名。這與package.path
有關。package.path
保存了require
尋找模組的路徑,以我的環境來看,其結果是:
print(package.path)
--> /usr/local/share/lua/5.4/?.lua;/usr/local/share/lua/5.4/?/init.lua;/usr/local/lib/lua/5.4/?.lua;/usr/local/lib/lua/5.4/?/init.lua;./?.lua;./?/init.lua
可以注意到當中有./?.lua
這段,也就是表示會從當前目錄尋找副檔名為.lua
的檔案。package.path
中的?
、;
、/
等等,與package.config
有關:
print(package.config)
--[[
/
;
?
!
-
--]]
載入、執行檔案,與主程式,為了確保函數安全的執行,有時候會對全局環境(_G
)多做一些處理,以保護全局環境。而雖然與主程式執行方式類似,但其實loadfile
和load
很像,對於模組檔案,也可以視為一個chunk
,這表示你可以為檔案保留一些僅該檔案可用的局部變數。
在示例之前,習慣上,僅會註冊一個與模組名稱同明得全局變數,並最後回傳它(return/export)。而其餘的僅僅是檔案中的局部變數,並不匯出。Lua自帶的模組os
、utf8
等都符合這樣的設計慣例。
hello1.lua
------ 隱藏的變數 --------
local __VERSION<const> = "1.0.0"
local example_name = "World"
local function helloWorld(name)
name = name or example_name
print("Hello, " .. name)
end
-------------
------------
--- 匯出 ---
------------
hello1 = {} -- 要匯出的變數
hello1.__VERSION = __VERSION
hello1.hello = helloWorld
-- 或是明確的註冊於環境裡
_ENV.hello1 = hello1
------------
--- 回傳 ---
--- 緩衝 ---
------------
return hello1
在上例中,於環境註冊了hello
結構,並回傳他。這個結構保留了欲匯出的其他變數--__VERSION
和helloWorld
函數(重新命名為hello()
),example_name
並不匯出。
來使用看看:
----- require ------
helloabc = require "hello1"
print(hello1) --> table: 0x55a1dcc6f1b0
print(helloabc) --> table: 0x55a1dcc6f1b0
----- 回傳結果被暫存於`package.loaded` ------
helloabc == package.loaded.hello1 --> true
hello1 == helloabc --> true
----- 無法使用未匯出的變數 -----
print(example_name) --> nil
print(helloWorld) --> nil
---- 可以使用匯出的變數 ------
print(hello1.hello) --> function: 0x55a1dcc70390
hello1.hello() --> Output: Hello, World
helloabc.hello() --> Output: Hello, World
※ 不過你也可以不註冊環境變數,僅僅是回傳,讓使用者自訂模組使用名稱。