从协程内省_ENV

时间:2019-07-23 09:48:20

标签: debugging reflection lua coroutine introspection

注意::我正在使用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之间的唯一区别是:

  1. 附加可选thread参数的处理;
  2. level参数的特殊设置取决于thread参数是否与运行的协程相同;和
  3. 在对threaddebug.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_nastymake_nice之间的唯一区别是,在后者中,闭包closure_B引用了不存在的全局变量(并且不执行任何操作)。

问::如何修改getvarvalue_enhanced,以便它能够像_ENV那样为nasty找到nice


编辑:更改了make_nastymake_nice中闭包的名称。


EDIT2:练习25.3(同一页)的措词在这里可能与我有关(我强调):

  

练习25.3:编写一个getvarvalue版本(清单25.1),该版本返回带有所有在调用函数中可见的变量 的表。 (返回的表不应包含环境变量;而应从原始环境继承它们。)

这个问题意味着,应该有一种方法来获取功能中仅可见的变量,该功能是否使用它们。这样的变量肯定会包含_ENV。 (作者是Lua的创作者之一,所以他知道他在说什么。)


1 我敢肯定,对本示例中发生的事情有更好了解的人将能够提出一种较为简单的方法来引发相同的行为。我在这里展示的示例是我偶然发现的情况的最简单形式。

2 个答案:

答案 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