访问非局部变量

调试库还提供了函数 getupvalue, 该函数允许我们访问一个被 Lua 函数所使用的非局部变量。与局部变量不同,被一个函数所调用的非局部变量即使在引用它的函数已经不活跃的情况下也会一直存在(闭包的实质)。因此,函数 getupvalue 的第一个参数不是栈层次,而是一个函数(更确切地说,是一个闭包)。函数 getupvalue 的第二个参数时变量索引,Lua 语言按照函数引用非局部变量的顺序对它们进行编号,但由于一个函数不能用同一名称访问两个非局部变量,所以实际上这个顺序是无关紧要的。

通过函数 debug.setupvalue 可以更新非局部变量的值。这个函数有三个参数: 一个闭包、一个变量索引和一个新值。 与函数 setlocal 一样,该函数返回变量名,如果变量索引超出范围则返回 nil

function getvarvalue (name, level, isenv)
	local value
	local found = false
	
	level = (level or 1)  + 1
	for i = 1, math.huge do
		local n, v = debug.getlocal(level, i)
		if not n then break end
		if n == name then
			value = v
			found = true
		end
	end
	if found then return "local", value end

	local func = debug.getinfo(level, "f").func
	for i = 1, math.huge do
		local n, v = deug.getupvalue(func, i)
		if not n then break end
		if n == name then return "upvalue", v end
	end
	
	if isenv then return "noenv" end
	
	local _, env = getvarValue("_ENV", level, true)
	if env then
		return "global", env[name]
	else
		return "noenv"
	end
end

使用方式为:

local a = 4, print(getvarvalue("a")) --> local	4
a = "xx", print(getvarvalue("a")) --> global xx

参数 level 指明在哪个栈层次中寻找函数, 1(默认值)意味着直接的调用者。

*该函数会首先检查局部变量。 如果有多个局部变量的名称与给定的名称相同,咋获取具有最大缩印的那个局部变量。 因此,函数必须执行完整循环。如果找不到指定名称的局部变量,那么就查找非局部变量。为了遍历非局部变量,该函数使用 debug.getinfo 函数获取调用闭包,然后遍历非局部变量。最后,如果还找不到指定名字的非局部变量,就检索全局变量:该函数递归地调用自己来访问合适的 *_ENV 变量并在相应的环境中查找指定的名字。

参数 isenv 避免了一个诡异的问题,该函数说明我们是否处于一个从 _ENV 变量中查询全局名称的递归调用中。一个不使用全局函数的函数可能没有上值 _ENV。 在这种情况下,如果我们试图把 _ENV 当做全局变量来查询,那么由于我们需要 _ENV 来得到其自身的值,所以可能会陷入无限递归循环。因此,当 isenv 为真且函数 getvarvalue 找不到局部变量或上值时, getvarvalue 就不再尝试全局变量。