修改 Lua 代码块环境:Lua 5.2

据我所知,在 Lua 5.2 中,环境存储在名为 _ENV 的 upvalue 中。这使得在运行前修改代码块的环境对我来说非常困难,但是在加载后使用它。

我想加载一个带有某些函数的文件,并使用块将这些函数注入到各种环境中。例如:

chunk = loadfile(“file”)

-- 注入块的定义
chunk._ENV = someTable -- 虚构的语法
chunk()

chunk._ENV = someOtherTable
chunk()

从 Lua 中是否可以实现这一点?我能找到的唯一修改此 upvalue 的示例是[使用 C api](https://stackoverflow.com/questions/19167986/how-do-i-set-via-the-lua-c-api-the-environment-table-for-a-chunk-of-lua-code-p)(来自 C api的 [另一个示例](http://lua.2524044.n2.nabble.com/Change-value-of-upvalue-from-within-function-td7642150.html)),但我正在尝试从 Lua 中实现这一点。这是可能的吗?

编辑:我不确定是否接受使用 debug 库的答案。[文档指出](http://www.lua.org/manual/5.2/manual.html#6.10)这些函数可能很慢。我这样做是为了效率,以便不必解析字符串(或文件,更糟糕)中的整个代码块只是为了将变量定义注入到各种环境中。

编辑:看起来这是不可能的:在 Lua 5.2 中重新创建 setfenv()

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

编辑:我认为更自然的方法是将所有代码块加载到单独的环境中。这些可以通过设置引用全局复制的块的元表来“继承”任何其他环境。这不需要加载后修改任何 upvalue,但仍允许具有这些函数定义的多个环境。

点赞
用户12048
用户12048

我不明白为什么你想避免使用 debug 库,但却乐意使用 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)"

它最初会继承 enclosing _ENV。给它一个新的:

local newEnv = newEnvForChunk(myChunk, _ENV)

并插入一个 'x' 的值:

newEnv.x = 99

现在当你运行这个块时,它应该看到 x 的值:

myChunk()

=> 99

2013-12-23 02:16:12
用户107090
用户107090

将代码块在不同环境中运行的最简单方法是显式设置环境变量,让代码块接收环境变量。在代码块顶部添加以下行即可实现:

_ENV=...

现在,您可以自由地调用 chunk(env1),然后是 chunk(env2)

这样,就不需要通过使用上值的 debug 魔法来实现了。

尽管如果代码块包含该行,它显而易见,但您可以通过编写适当的读取器函数,在加载时添加该行,然后再添加文件内容。

2013-12-23 10:29:33
用户405017
用户405017

如果您不想修改您的代码块(根据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倍。

2019-08-30 15:28:51