LUA - 创建元表,每个子表再次成为元表

Posted

技术标签:

【中文标题】LUA - 创建元表,每个子表再次成为元表【英文标题】:LUA - Create Meta Table with every sub table beeing a meta table again 【发布时间】:2020-12-02 10:56:50 【问题描述】:

但这肯定会让人感到困惑。

我对 LUA 还是很陌生,我还没有做太多工作的一件事是元表。

我需要找到一种方法来创建一个元表,该表运行一个编辑值的函数。如果我停留在“一个级别”上,这不是问题,所以第一个索引。然后我可以简单地使用 __newindex 来运行它。但我想做的是在 any value 改变时运行该函数。

这将需要某种方式将元表中的任何表设置为与“主”元表运行相同功能的元表

在我的用例中,这将是一个“保存”功能:

function MySaveFunction(tbl)
   FileSave(my_settings_path, tbl)
end

MyTable = setmetatable()
MyTable.Value = value --> run MySaveFunction(MyTable.Value)

MyTable.SubTable =  --> run setmetatable() on SubTable
MyTable.SubTable.Value = value --> run MySaveFunction(MyTable.SubTable.Value)

MyTable.SubTable.SubSubTable =  --> run setmetatable() on SubSubTable
MyTable.SubTable.SubSubTable.Value = value --> run MySaveFunction(MyTable.SubTable.SubSubTable.Value)

MyTable.SubTable.SubSubSubTable =  --> run setmetatable() on SubSubSubTable
MyTable.SubTable.SubSubSubTable.Value = value --> run MySaveFunction(MyTable.SubTable.SubSubSubTable.Value)

希望有人可以帮助我<.>

【问题讨论】:

应该MyTable.Value = value2 再次调用MySaveFunction 吗? 没错,每次编辑/更改/创建/删除都应该调用它 您需要使用代理表并使用附加上下文对其进行传输。类似于this answer 的东西应该可以完成这项工作。如果到时候你还没有答案,我下班后试试。 【参考方案1】:

首先要注意的是__newindex__index 元方法仅在它们处理目标表中的nil 值时才会触发。如果你想跟踪每一个变化,你不能只使用__newindex,因为一旦你写了一个值,后续的调用将不会做任何事情。相反,您需要使用代理表。

另一个需要考虑的重要事情是跟踪访问成员的路径。

最后但同样重要的是,您需要记住使用原始访问功能,例如rawget 在实现您的处理程序时。否则,您可能会遇到堆栈溢出或其他奇怪的行为。

让我们举一个简单的例子来说明问题:

local mt = 
function mt.__newindex (t, key, value)
    if type(value) == "table" then
        rawset(t, key, setmetatable(value, mt)) -- Set the metatable for nested table
        -- Using `t[key] = setmetatable(value, mt)` here would cause an overflow.
    else
        print(t, key, "=", value) -- We expect to see output in stdout for each write
        rawset(t, key, value)
    end
end

local root = setmetatable(, mt)
root.first = 1                   -- table: 0xa40c30 first   =   1
root.second = 2                  -- table: 0xa40c30 second  =   2
root.nested_table =            -- /nothing/
root.nested_table.another = 4    -- table: 0xa403a0 another =   4
root.first = 5                   -- /nothing/

现在,我们需要处理它们。让我们从一种创建代理表的方法开始:

local
function make_proxy (data)
    local proxy = 
    local metatable = 
        __index = function (_, key) return rawget(data, key) end,
        __newindex = function (_, key, value)
            if type(value) == "table" then
                rawset(data, key, make_proxy(value))
            else
                print(data, key, "=", value) -- Or your save function here!
                rawset(data, key, value)
            end
        end
    
    return setmetatable(proxy, metatable) -- setmetatable() simply returns `proxy`
end

这样你就有了三个表:proxymetatabledata。用户访问 proxy,但由于每次访问时它都是空的,因此调用了 metatable 中的 __index__newindex 元方法。这些处理程序访问 data 表以检索或设置用户感兴趣的实际值。

以与之前相同的方式运行此程序,您将获得改进:

local root = make_proxy
root.first = 1                   -- table: 0xa40c30 first   =   1
root.second = 2                  -- table: 0xa40c30 second  =   2
root.nested_table =            -- /nothing/
root.nested_table.another = 4    -- table: 0xa403a0 another =   4
root.first = 5                   -- table: 0xa40c30 first   =   5

这应该让您大致了解为什么应该在此处使用代理表以及如何处理它的元方法。

剩下的是如何识别您正在访问的字段的路径。该部分在another answer to another question 中进行了介绍。我认为没有理由复制它。

【讨论】:

非常感谢!它仍然有点令人困惑,但它至少给了我一个很好的指导来解决问题! @frenkey,我认为还有一件事值得澄清,因为您写的内容表明您可能会误解它:当您执行 setmetatable(a, b) 时,您分配了一个表 b 来充当表 @987654335 的元表@。 a 不会成为元表 - 它分配了一个元表。无论如何,这些都不是最简单的事情,慢慢来,阅读,自己尝试。一旦您将头放在元表周围,元表就会非常有趣! @frenkey,我更新了代理示例以希望使其更具描述性,请查看。 谢谢!我会试着绕着它转!我确实已经看到了元表的潜力,但它与我之前使用的其他所有东西都大不相同

以上是关于LUA - 创建元表,每个子表再次成为元表的主要内容,如果未能解决你的问题,请参考以下文章

10----元表

Lua中的元表与元方法

lua 元表操作

lua元表(metatable)

lua元表详解

一文读懂Lua元表