环境教程

lua-users home
wiki

与存储在解释器中特殊数据结构中的局部变量不同,全局变量只是存储在一个表中。Lua 中一个有用的功能是能够更改每个函数的这个表,这样函数就会看到一组不同的全局变量。

默认全局表存储在它本身的 "_G" 键下,如果你想获得它的实际引用,这很有用。

Lua 5.2 中的环境工作方式与 5.1 大不相同。这里将解释两种方式。

Lua 5.2 中的环境

函数的环境存储在一个名为 _ENV 的上值中。例如,以下是一个将环境设置为自定义环境并使用其中变量的函数

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 上值,并且它内部的任何嵌套函数都可以看到它。你可以假装加载过程类似于以下方式

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 中,环境是它们自己的东西,与局部变量或上值无关。相反,每个函数都有一个与之关联的环境表,可以使用 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 的方式的设计理念是函数的环境应该是私有的,而不是在没有调试库的情况下从任何地方都可以访问,因此它可以被认为更安全。


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2014 年 6 月 26 日凌晨 12:48 GMT (差异)