Lua - __newindex元方法用于现有索引?

最近我了解了Lua中methatables的存在,并尝试使用它们来避免表中的“重复项”。我一直在寻找相关信息,但是一直没有发现我需要的东西,所以我来请求帮助。

  • 我想要做的事情及其目的:

这是用于开发WoW插件的。 我希望创建一个工具,可以在创建变量或函数时在全局范围内弹出警告。因为可能会发生其他插件的命名冲突,所以需要避免使用全局范围。从那里开始,我还希望重定向所有与_G表的转移。因此,当用户在全局范围内创建变量或函数时,工具将捕获该值,将其存储在一个表中而不是_G中,并且每当用户尝试从_G中访问任何内容时,工具将首先在该表中查找它,仅在垫底线_G中找不到时才会使用_G。 因此,用户无需担心正确的封装或命名,工具会为其处理所有内容。

  • 我已经成功完成的事情:

我正在_G上设置__newindex元方法来捕获全局作用域的变量和函数,并在插件加载结束时删除该元方法,以避免其他插件使用它。 对于_G转移的“间接”,我已经知道如何使用__index来尝试在尝试使用_G之前给出另一个表中存储的值。

  • 我遇到的问题:

这可以很好地工作,但仅适用于_G中不存在的变量和函数。每当为_G表中已存在的键分配值时,它无法工作(由于明显的原因)。我希望能够捕获这些情况,基本上使其不可能实际上覆盖_G的内容,而是使用一种类似于“重载”的方式(但用户甚至不必知道)。

  • 我尝试过的方法:

我尝试挂钩rawset,看它是否会自动调用,但它似乎没有。

我没有找到关于Lua中_G表的详细说明,主要是因为它的短名称。我肯定有某些信息可以帮助我实现想要的功能,但目前我有点迷失和失望。 所以,我想知道是否有任何方法可以“捕获”所有“隐式调用rawset”,以便在让它完成其工作之前进行一些检查。我收集到的信息是似乎没有__existingindex的元方法,您知道如何做到这一点吗?

点赞
用户3125367
用户3125367

虽然您已经在评论中得到了答案,但是在 Lua 5.1 中有关于“环境”更深入的概念。环境是一个附加到函数的表格,该函数将其“全局”读写重定向到此表格。_G 只是对“全局”环境的引用,即主线程(主协程)的环境。它可以被清除为 nil 而没有非明显的影响,因为它只是一个变量,类似于 T = { }; T._T = T

特别地,_G == getfenv(0),除非有人更改它的含义(参见 getfenv() 参考其参数为何)。当脚本被加载时,它隐式绑定到全局环境。由于 Lua 的顶层范围(又称为主块)只是一个匿名函数,因此其环境可以随时重新绑定到任何其他表:

-- x.lua

local T = { }
local shadow = setmetatable({ }, { __index = getfenv(0) })

local mt = {
    __index = shadow,
    __newindex = function (t, k, v)
        -- while T is empty, this will be called every time you 'set global'
        -- do whatever you want here
        shadow[k] = v
        print(tostring(k)..' is set to '..tostring(v))
    end
}

setmetatable(T, mt) -- T[k] goes to shadow, then to _G
setfenv(1, T)       -- change the environment of this module to T

hello = "World"     -- 'hello is set to World'
print(T.hello)      -- 'World'
print(_G.hello)     -- 'nil', because we didn't even touch _G

hello = 3.14        -- 'hello is set to 3.14'
hello = nil         -- 'hello is set to nil'
hello = 2.72        -- 'hello is set to 2.72'

function f()        -- 'f is set to function: 0x804a00'
    print(hello)
end

f()                 -- '2.72'

assert(getfenv(f) == getfenv(1))
assert(getfenv(f) == T)

setfenv(f, _G)      -- return f back to _G
f()                 -- 'nil'

使用此方法,您可以完全隐藏其他模块的元表机制。请注意,在 setmetatable() 调用之后更改 mt 不会产生任何效果。

还要记住,setfenv() 下方定义的所有函数共享同一环境 T(这不适用于通过 require 加载的外部函数/模块或从这些函数/模块返回的函数,因为环境继承是词法的)。

__newindex 设置为 _G 可能暂时有效,但要记住,在调用之间调用的任何函数可能尝试设置全局变量,这可能会干扰您的逻辑或以微妙的方式破坏它们的逻辑。发生冲突的可能性应该很低,因为破坏 _G 是一个坏主意,每个人都知道这一点。

2017-08-22 03:12:34