環境教學手冊

lua-users home
wiki

與儲存在執行器特殊資料結構中的區域變數不同,全域變數只儲存在表格中。Lua 有個特別的功能,可以讓這個表格在每個函式中做變更,因此函式會看到不同的全域變數。

預設全域表格儲存在 "_G" 鍵值中,如果你想要取得它實際的 ref,這將會很有用。

Lua 5.2 中環境運作的方式與 5.1 非常不同。這兩種方式將會在這裡解說說明。

Lua 5.2 中的環境

函式的環境儲存在一個命名為 _ENV 的 upvalue 中。身為一個範例,這裡有一個函式將環境設定為自訂環境,並使用其中的變數

print(_ENV == _G) -- prints true, since the default _ENV is set to the global table

a = 1

local function f(t)
  local print = print -- since we will change the environment, standard functions will not be visible

  local _ENV = t -- change the environment. without the local, this would change the environment for the entire chunk

  print(getmetatable) -- prints nil, since global variables (including the standard functions) are not in the new env
  
  a = 2 -- create a new entry in t, doesn't touch the original "a" global
  b = 3 -- create a new entry in t
end

local t = {}
f(t)

print(a, b) --> 1 nil
print(t.a, t.b) --> 2 3

載入區塊時,頂層函式會取得一個新的 _ENV upvalue,且任何巢狀函式都能看到它。你可以想像載入運作類似這樣

local _ENV = _G
return function (...) -- this function is what's returned from load
  -- code you passed to load goes here, with all global variable names replaced with _ENV lookups
  -- so, for example "a = b" becomes "_ENV.a = _ENV.b" if neither a nor b were declared local
end
現在你可以看到 _ENV 是個一般的區域變數,所有函式是如何存取 _ENV,以及為什麼如果其中一個函式變更 _ENV,其他在已載入區塊中的函式都會看到變更。那就是為什麼如果想要函式只變更自己的環境,你必須建立一個新的 _ENV 區域變數來遮罩原來的。

在大部分情況下,你不必使用環境,除非你想對所載入的區塊實施沙盒,方便存取某些函式 (透過讓它們看起來像是全域的) 或為了安全因素讓它看不到不安全的函式。這就是為什麼 5.2 的 load 函式會將參數設定為允許你設定區塊的 _ENV 成為自訂表格,而非 _G。

local sandbox_env = {
  print = print,
}

local chunk = load("print('inside sandbox'); os.execute('echo unsafe')", "sandbox string", "bt", sandbox_env)

chunk() -- prevents os.execute from being called, instead raises an error saying that os is nil

如果你實際上想建立一個沙盒來執行不可信的程式碼,請記得,很容易會忽略很多能被利用的東西,以及你會需要一些限制 CPU 使用率和記憶體的方法。

Lua 5.1 中的環境

在 Lua 5.1 中,環境有自己的形式,與區域變數或 upvalue 無關。反之,每個函式有一個與它有關的環境表格,可使用標準函式 getfenv/setfenv 來處理。

getfenvsetfenv 兩者都接受函式或堆疊層級 (其中 1 是目前函式,2 是呼叫目前函式的函式,依此類推)。setfenv 有第二個參數,用來接受新的 env 表格,而 getfenv 會回傳函式的當前 env 表格。

先前範例改寫為 5.1

print(getfenv(1) == _G) -- prints true, since the default env is set to the global table

a = 1

local function f(t)
  local print = print -- since we will change the environment, standard functions will not be visible

  setfenv(1, t) -- change the environment

  print(getmetatable) -- prints nil, since global variables (including the standard functions) are not in the new env
  
  a = 2 -- create a new entry in t, doesn't touch the original "a" global
  b = 3 -- create a new entry in t
end

local t = {}
f(t)

print(a, b) --> 1 nil
print(t.a, t.b) --> 2 3

而沙盒範例改寫為 5.1

local sandbox_env = {
  print = print,
}

local chunk = loadstring("print('inside sandbox'); os.execute('echo unsafe')")
setfenv(chunk, sandbox_env)

chunk() -- prevents os.execute from being called, instead raises an error saying that os is nil

有時候會覺得 5.1 的方式比較簡單且用途較廣,但它也需要對環境進行個別處理 (而非使用現有的區域變數系統)。另外,5.2 的方式是設計成在沒有 debug 函式庫的情況下,從各地存取函式的環境,因此可以被認為較為安全。


近期變更 · 偏好設定
編輯 · 歷次修改
最近於 2014 年 6 月 26 日 格林威治標準時間 12:48 am 編輯 (差異)