Lua 5.4.4中load()指定的_ENV为何未被内部调用函数使用?
Lua 5.4.4:load()自定义ENV无法作用于外部函数的问题
问题描述
当用load()编译字符串代码块并传入自定义env参数时,代码块内部调用的外部函数is_env_g()并不会使用这个env作为自身的_ENV,示例代码如下:
-- 创建继承自父环境的新环境 function new_env(parent) return setmetatable({}, { __index = parent }) end function run_lua_string(luastr, env) local f = load(luastr, '', 't', env) -- 用指定env作为代码块的_ENV f() -- 执行编译后的代码 end function is_env_g() print(_ENV == _G) end local luastr = [[ print(_ENV == _G) -- 输出false,符合预期 is_env_g() -- 预期输出false,但实际输出true ]] -- 准备新环境:继承自当前_ENV(即_G),但不等于_G local env = new_env(_ENV) -- 在自定义env中执行代码块 run_lua_string(luastr, env)
运行后,代码块内的print(_ENV == _G)输出false符合预期,但调用is_env_g()却输出true,不符合预期。
问题原因
Lua中函数的_ENV是在函数定义时就绑定好的,和调用时的环境无关。is_env_g()是在全局环境_G中定义的,所以它的_ENV默认就是_G,不管你在哪个环境里调用它,它内部的_ENV都不会改变。
而load()编译的代码块是把传入的env作为自己的_ENV,所以代码块内部的_ENV是自定义的,但调用外部函数时,外部函数依然使用自己定义时的_ENV。
替代解决方法(除了显式传参)
方法1:使用debug库修改函数的upvalue
Lua的debug库可以修改函数的upvalue,我们可以把is_env_g()的_ENV临时替换成目标env,执行完再恢复:
function run_lua_string(luastr, env) local f = load(luastr, '', 't', env) -- 保存is_env_g原来的_ENV local old_env = debug.getupvalue(is_env_g, 1) -- 将is_env_g的第一个upvalue(即_ENV)设置为当前env debug.setupvalue(is_env_g, 1, env) f() -- 恢复原来的_ENV,避免影响其他调用 debug.setupvalue(is_env_g, 1, old_env) end
注意:debug库属于底层工具,生产环境使用要谨慎,可能会破坏代码的封装性和可维护性,而且如果有并发调用,可能会出现竞争问题。
方法2:将函数定义放到目标环境中
如果允许,可以把is_env_g()的定义放到load()编译的代码块里,这样它的_ENV就是自定义的env:
local luastr = [[ function is_env_g() print(_ENV == _G) end print(_ENV == _G) -- 输出false is_env_g() -- 输出false,符合预期 ]]
但这种方法的缺点是每次编译代码块都会重新定义函数,无法复用外部的is_env_g()。
方法3:让函数动态捕获调用环境(闭包方式)
可以定义一个生成函数,在目标环境中生成使用当前_ENV的is_env_g():
function make_is_env_g() return function() print(_ENV == _G) end end -- 在代码块中生成函数并调用 local luastr = [[ local is_env_g = make_is_env_g() print(_ENV == _G) -- 输出false is_env_g() -- 输出false,符合预期 ]]
这种方式需要在目标环境中生成函数,好处是不需要修改原函数,也不用依赖debug库。
内容的提问来源于stack exchange,提问作者VoodooJuJu
相关产品推荐
相关产品推荐

