【问题标题】:Modify Lua Chunk Environment: Lua 5.2修改 Lua Chunk 环境:Lua 5.2
【发布时间】:2014-01-11 04:50:15
【问题描述】:

据我了解,在 Lua 5.2 中,环境存储在名为 _ENV 的上值中。这让我在运行之前修改块的环境感到非常困惑,但在加载它之后。

我想加载一个包含一些函数的文件,并使用该块将这些函数注入到各种环境中。示例:

chunk = loadfile( "file" )

-- Inject chunk's definitions
chunk._ENV = someTable -- imaginary syntax
chunk( )

chunk._ENV = someOtherTable
chunk( )

这可以在 Lua 中实现吗?我能找到的修改这个upvalue的唯一例子是with the C api(另一个example from C api),但我试图在Lua中做到这一点。这可能吗?

编辑:我不确定是否接受使用调试库的答案。 docs state 表示功能可能很慢。我这样做是为了提高效率,这样就不必为了将变量定义注入各种环境而从字符串(或文件,甚至更糟)中解析整个块。

编辑:看起来这是不可能的:Recreating setfenv() in Lua 5.2

编辑:我想最好的方法是绑定一个可以修改环境的 C 函数。虽然这是一种更烦人的方式。

编辑:我相信更自然的方法是将所有块加载到单独的环境中。这些可以通过设置引用块的全局副本的元表被任何其他环境“继承”。这不需要在加载后进行任何上值修改,但仍然允许具有这些函数定义的多个环境。

【问题讨论】:

  • 请注意,在您插入的链接中,有一个comment by one of Lua authors
  • 我看到了评论,但我几乎不知道他的意思。我没有5.1的经验来理解。 @akavel
  • 呃,实际上我也不是 100% 确定,但据我了解,在实践中,这大致意味着您似乎已经从其他人那里得到了什么:通过调试库或 C API 是可行的(事实上​​,debug 确实使用了 C API),尽管它故意变得有点困难和不明显,因此它不会在常规的日常 Lua 代码中被过度使用。来自 lhf 的答案具有很高的可信度,并且可以洞察原始意图。

标签: lua


【解决方案1】:

允许一个块在不同环境中运行的最简单方法是明确这一点并让它接收一个环境。在块的顶部添加这一行可以实现:

_ENV=...

现在您可以随意拨打chunk(env1)chunk(env2)

在那里,没有 debug 具有上值的魔法。

虽然很清楚您的块是否包含该行,但您可以在加载时添加它,方法是编写一个合适的读取器函数,首先发送该行,然后发送文件的内容。

【讨论】:

  • 非常感谢您的回答。它看起来确实不错,但是将该行添加到文件中对于脚本编写来说很糟糕。所以关于在读取文件之前插入这一行的评论真的很酷。我要去试试这个。
【解决方案2】:

我不明白您为什么要避免使用调试库,而您却乐于使用 C 函数(在沙盒中都不可能。)

可以使用debug.upvaluejoin:

function newEnvForChunk(chunk, index)
  local newEnv = {}
  local function source() return newEnv end
  debug.upvaluejoin(chunk, 1, source, 1)
  if index then setmetatable(newEnv, {__index=index}) end
  return newEnv
end

现在像这样加载任何块:

local myChunk = load "print(x)"

它将最初继承封闭的_ENV。现在给它一个新的:

local newEnv = newEnvForChunk(myChunk, _ENV)

并为“x”插入一个值:

newEnv.x = 99

现在,当您运行块时,它应该会看到 x 的值:

myChunk()

=> 99

【讨论】:

  • 哦,使用 Debug 库真的可以吗?我实际上并没有为测试环境设置沙盒,我这样做是为了发布版本。具体来说,我在游戏引擎中使用它,其中单独的环境充当基于聚合的模型中的组件。每个组件环境都应该有InitUpdateShutdown 的定义。理想情况下,每个“组件文件”中的所有局部变量都可以实例化到单独的环境中,这就是我想使用块注入环境的原因。您认为有更好的方法吗?还是可以接受?
  • 我还稍微编辑了我说我不想接受调试库答案的问题。也可以使用debug.setupvalue 吗?看起来这可能要简单得多。
  • 如果有帮助,我尝试了debug.setupvalue,效果很好!我会接受你的回答。
  • @RandyGaul 如果您有多个块共享相同的_ENV(这是默认设置),那么debug.setupvalue 将影响所有块。如果您创建独立的_ENVs(与debug.upvaluejoin),那么它们将不会相互影响。
  • @RandyGaul 重新调试功能开始缓慢:是的,但加载新块也是如此。两者都只在启动时完成一次。
【解决方案3】:

如果您不想修改您的块(根据 LHF 的最佳答案),这里有两种选择:

设置一个空白环境,然后将其环境动态更改为您的环境

function compile(code)
   local meta = {}
   local env = setmetatable({},meta)
   return {meta=meta, f=load('return '..code, nil, nil, env)}
end

function eval(block, scope)
   block.meta.__index=scope
   return block.f()
end

local block = compile('a + b * c')
print(eval(block, {a=1, b=2, c=3})) --> 7
print(eval(block, {a=2, b=3, c=4})) --> 14

设置一个空白环境,每次都用自己的值重新设置它的值

function compile(code)
   local env = {}
   return {env=env, f=load('return '..code, nil, nil, env)}
end

function eval(block, scope)
   for k,_ in pairs(block.env) do block.env[k]=nil end
   for k,v in pairs(scope) do block.env[k]=v end
   return block.f()
end

local block = compile('a + b * c')
print(eval(block, {a=1, b=2, c=3})) --> 7
print(eval(block, {a=2, b=3, c=4})) --> 14

请注意,如果微优化很重要,第一个选项的速度大约是 _ENV=... 答案的 2✕,而第二个选项的速度大约是 8–9✕。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-27
    • 2013-04-06
    • 2015-04-14
    • 1970-01-01
    • 1970-01-01
    • 2013-10-03
    • 2012-04-02
    • 2012-11-04
    相关资源
    最近更新 更多