调试库开提供了函数 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 = debug.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
(默认值)意味着直接的调用者。代码中多加的 1
将层次纠正为包括 getvarvalue
自己。
该函数首先查找局部变量。如果有多个局部变量的名称与给定的名称相同,则获取具有最大索引的那个局部变量。因此,函数必须执行完整的循环。如果找不到指定名称的局部变量,那么就查找非局部变量。为了遍历非局部变量,该函数使用 debug.getinfo
函数获取调用闭包,然后遍历非局部变量。最后,如果还是找不到指定名字的非局部变量,就检索全局变量:该函数递归的调用自己来访问合适的 _ENV
变量并在相应环境中查找指定的名字。
参数 isenv
避免了一个诡异的问题。该参数用于说明我们是否处于一个从 _ENV
变量中查询全局名称的递归调用中。一个不使用全局变量的函数可能没有上值 _ENV
。在这种情况下,如果我们试图把 _ENV
当做全局变量来查询,那么由于我们需要 _ENV
来得到其自身的值,所以可能会陷入无限递归循环。因此,当 isenv
为真且函数 getvarvalue
找不到局部变量或上值时, getvarvalue
就不应该再尝试全局变量。