【问题标题】:Lua - __newindex metamethod for existing index?Lua - 现有索引的 __newindex 元方法?
【发布时间】:2017-08-12 21:35:44
【问题描述】:

我最近了解到 lua 中存在 methatables,我一直在玩弄它们,直到我想到一个想法:是否可以使用它们来尝试避免表中的“重复”?我找了又找,到目前为止找不到我要找的东西,所以我在这里。

  • 这就是我想要做的事情和目的:

它用于魔兽插件编程。 我想制作一个在全局范围内创建变量或函数时会提示警告的工具(以避免使用它,因为可能与其他插件发生命名冲突)。 我可以从那里做的另一件事是将所有传输重定向到 _G 表。因此,当用户在全局范围内创建变量或函数时,该工具会捕获它,将其存储在表中而不是 _G 中,并且每当用户尝试从 _G 访问某些内容时,该工具会首先在所说的桌子;并仅使用 _G 作为后备。 这样,用户就不必担心适当的封装或命名,该工具会为他处理一切。

  • 我已经做到了:

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

  • 我遇到的问题:

这很好用,但仅适用于 _G 中尚不存在的变量和函数。每当为 _G 表中已经存在的键分配值时,它都不起作用(原因很明显)。我确实希望能够捕捉到这些情况,并且基本上不可能真正覆盖 _G 的内容,而是使用一种“重载”(但用户甚至不必知道这一点)。

  • 我尝试了什么:

我试图挂钩 rawset,看看它是否被自动调用,它似乎不是。

我无法在 lua 中找到很多关于 _G 表的文档,主要是因为名称短。我确信某处一定存在某些东西,我可能可以使用这些信息以我想要的方式完成事情,但目前我只是有点迷失并且没有想法。 所以是的,我想知道是否有任何方法可以“捕获”所有“对 rawset 的隐式调用”,以便在让它执行其工作之前进行一些检查。我收集到显然没有 __existingindex 或其他东西的元方法,所以你知道有什么方法吗?

【问题讨论】:

  • 您要查找的内容称为代理表。您需要创建一个新的空表并将其分配给_G,同时将__newindex__index 设置为搜索原始_G 表的函数,该表以某种方式捕获。您的 __newindex 函数不应允许重新定义现有键。我从来没有做过这样的事情,所以我不能告诉你这样的解决方案有多稳定。这肯定会很慢。如果你愿意,我可以写一个完整的。编辑:查看strict.lua 的实现以获得灵感。
  • 感谢您的回答;但是我不确定它将如何解决我的问题。能够知道 _G 中是否已经存在密钥不是问题(我可以简单地在我的工具文件中有一个本地 _G 并检查 local_G[key] ~= nil);问题是我不知道如何知道何时重新分配已经存在的密钥。我的工具的目标是“初始化并忘记”。我不想在尝试将某些东西分配给全局范围时调用一个函数,我希望该工具在我这样做时“检测”,所以它可以告诉我不要这样做
  • 如果能够以某种方式做到这一点,那么每次将某些东西声明为全局时,它都会进入与 _G 不同的表中,那就太好了。我知道我可以通过在每个文件的开头获取一个本地“假”_G 来做到这一点,但这就是我想避免这样做
  • 值得注意的是,一个表现良好的插件应该返回一个库表,而不是创建全局变量。
  • 对于解释器,全局赋值a=1 看起来像_G['a']=1,因此第一个注释是解决方案。此外,它是此处提供的信息的重复 lua.org/pil/13.4.4.html 。此外,参考书(可能除了 C 部分),它包含您关于 _G 的问题的答案。您可能应该完整阅读它。

标签: indexing lua metatable global-scope meta-method


【解决方案1】:

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

具体来说,_G == getfenv(0),除非有人改变了它的含义(参见getfenv() 参考以了解它的论点)。加载脚本时,它会隐式绑定到全局环境。由于 Lua 的顶级作用域(又名 main chunk)只是一个匿名函数,它的环境可以随时反弹到任何其他表:

-- 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'

通过这种方法,您可以完全隐藏其他模块的元表机制。请注意,mt 的更改在调用setmetatable() 后无效。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-02
    • 2012-12-15
    • 1970-01-01
    • 1970-01-01
    • 2014-09-04
    相关资源
    最近更新 更多