注意::我正在使用Lua 5.3版。
该问题是由编程在Lua (第4版)中的练习25.1(第264页)引起的。该练习的内容如下:
练习25.1:使
getvarvalue
(清单25.1)适应于不同的协程(如debug
库中的函数)。
该练习所引用的功能getvarvalue
在下面逐字复制。
-- Listing 25.1 (p. 256) of *Programming in Lua* (4th ed.)
function getvarvalue (name, level, isenv)
local value
local found = false
level = (level or 1) + 1
-- try local variables
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
-- try non-local variables
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 -- avoid loop
-- not found; get value from the environment
local _, env = getvarvalue("_ENV", level, true)
if env then
return "global", env[name]
else -- no _ENV available
return "noenv"
end
end
下面是此功能的增强版,它实现了练习中指定的其他功能。此版本接受可选的thread
参数,该参数应为协程。此增强版本与原始getvarvalue
之间的唯一区别是:
thread
参数的处理; level
参数的特殊设置取决于thread
参数是否与运行的协程相同;和thread
和debug.getlocal
的调用中以及在递归调用中传递debug.getinfo
参数。(我已通过编号注释在源代码中标记了这些差异。)
function getvarvalue_enhanced (thread, name, level, isenv)
-- 1
if type(thread) ~= "thread" then
-- (thread, name, level, isenv)
-- (name, level, isenv)
isenv = level
level = name
name = thread
thread = coroutine.running()
end
local value
local found = false
-- 2
level = level or 1
if thread == coroutine.running() then
level = level + 1
end
-- try local variables
for i = 1, math.huge do
local n, v = debug.getlocal(thread, level, i) -- 3
if not n then break end
if n == name then
value = v
found = true
end
end
if found then return "local", value end
-- try non-local variables
local func = debug.getinfo(thread, level, "f").func -- 3
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 -- avoid loop
-- not found; get value from the environment
local _, env = getvarvalue_enhanced(thread, "_ENV", level, true) -- 3
if env then
return "global", env[name]
else
return "noenv"
end
end
此功能相当不错,但我发现一个奇怪的情况 1 在此失败。下面的函数make_nasty
生成一个协程,其中getvarvalue_enhanced
找不到_ENV
变量;即返回"noenv"
。 (作为nasty
基础的功能是闭包closure_B
,它依次调用闭包closure_A
。然后是closure_A
产生。)
function make_nasty ()
local function closure_A () coroutine.yield() end
local function closure_B ()
closure_A()
end
local thread = coroutine.create(closure_B)
coroutine.resume(thread)
return thread
end
nasty = make_nasty()
print(getvarvalue_enhanced(nasty, "_ENV", 2))
-- noenv
相反,几乎相同的函数make_nice
生成一个协程,getvarvalue_enhanced
成功找到了一个_ENV
变量。
function make_nice ()
local function closure_A () coroutine.yield() end
local function closure_B ()
local _ = one_very_much_non_existent_global_variable -- only difference!
closure_A()
end
local thread = coroutine.create(closure_B)
coroutine.resume(thread)
return thread
end
nice = make_nice()
print(getvarvalue_enhanced(nice, "_ENV", 2))
-- upvalue table: 0x558a2633c930
make_nasty
和make_nice
之间的唯一区别是,在后者中,闭包closure_B
引用了不存在的全局变量(并且不执行任何操作)。
问::如何修改getvarvalue_enhanced
,以便它能够像_ENV
那样为nasty
找到nice
?
编辑:更改了make_nasty
和make_nice
中闭包的名称。
EDIT2:练习25.3(同一页)的措词在这里可能与我有关(我强调):
练习25.3:编写一个
getvarvalue
版本(清单25.1),该版本返回带有所有在调用函数中可见的变量 的表。 (返回的表不应包含环境变量;而应从原始环境继承它们。)
这个问题意味着,应该有一种方法来获取功能中仅可见的变量,该功能是否使用它们。这样的变量肯定会包含_ENV
。 (作者是Lua的创作者之一,所以他知道他在说什么。)
1 我敢肯定,对本示例中发生的事情有更好了解的人将能够提出一种较为简单的方法来引发相同的行为。我在这里展示的示例是我偶然发现的情况的最简单形式。
答案 0 :(得分:1)
local function inner_closure () coroutine.yield() end
local function outer_closure ()
inner_closure()
end
下面的函数make_nasty生成一个协程,其getvarvalue_enhanced无法找到_ENV变量;即返回“ noenv”
这是正确的行为。
闭包outer_closure
具有高值inner_closure
,但没有高值_ENV
。
这就是词法作用域的工作方式。
没关系,某些闭包没有_ENV
升值。
在您的示例中,闭包inner_closure
未在outer_closure
的正文中定义。
inner_closure
未嵌套在outer_closure
中。
答案 1 :(得分:1)
不可能。
如果闭包不使用全局环境_ENV
,那么闭包就不会有任何升值。
类似
的功能local something = 20
local function noupval(x, y)
return x * y
end
不需要或没有任何升值,甚至不需要全球环境。
这个问题意味着无论是否使用函数,都应该有一种方法来获取仅从函数可见的变量。
确实没有。您可以通过查看luac -p -l <your_code.lua>
的输出来轻松确认这一点,更确切地说,是查看每个函数的上值。
如果有的话,我认为在其中使用 visible 这个词有点误导。可见性仅在创建闭包时才重要,但是一旦闭包,闭包仅具有一组可以访问的升值。
练习25.3:编写getvarvalue的版本(清单25.1),该版本返回具有所有在调用函数中可见的变量的表。 (返回的表不应包含环境变量;而应从原始环境继承它们。)
您可能对这项运动有误解;我的理解方式是这样的:
local upvalue = 20
local function foo()
local var = upvalue -- Create 1 local and access 1 upvalue
type(print) == "function" -- Access _ENV so it becomes an upvalue
return getvarvalue_enhanced()
end
上面的代码将返回{var = 20, upvalue = 20, _ENV = <Proxy table to _ENV>}
毕竟,它专门询问有关 calling 函数,而不是您作为参数传递的函数。
这不会改变以下事实:您仍然可以访问_ENV
。如果您不使用任何全局变量,则该函数将不会引用任何_ENV
。