关于 __newindex 中“Lua 不执行基本赋值操作” 在 2.4 中的含义

来自 https://www.lua.org/manual/5.3/manual.html 请查看 2.4 节中关于元方法操作 __newindex 的如下语句:

__newindex: 对表执行索引赋值操作 table[key] = value。和索引事件一样,如果表不是表或 key 不在表中,会产生这个事件。在 table 中查找元方法。

和索引一样,这个事件的元方法也可以是一个函数或一个表。如果是函数,会使用 table、key 和 value 作为参数进行调用。如果是一个表,Lua 会对这个表进行索引赋值,使用相同的 key 和 value。(这个赋值是普通赋值,不是原始赋值,因此可能会引发另一个元方法。)

只要有 __newindex 元方法,Lua 就不会执行基本赋值操作。(如果必要,元方法本身可以调用 rawset 进行赋值。)

因此我想请问一下下面这句话具体是什么意思:

“Lua 不执行基本赋值操作。(如果必要,元方法本身可以调用 rawset 进行赋值。)”

这是不是意味着,如果值为数值,即原语类型,它不会通过元方法事件被赋值给提供的表,而我们必须使用 rawget 或其他方式?这对我来说非常令人困惑和矛盾。

点赞
用户8294610
用户8294610

我想给你展示一些例子来帮你搞清楚这个问题。

原始赋值的例子:

local test = {}
test['x'] = 1 -- 等同于 rawset(test, 'x', 1)
print(test['x']) -- 1
print(rawget(test,'x')) -- 1

当表 test 没有 __newindex 元方法时,原始赋值的代码 test['x'] = 1 等同于 rawset(test, 'x', 1)

接着是 __newindex 元方法的例子:

local test = {}
setmetatable(test, {__newindex = function(t,key,value) end})
test['x'] = 1
print(test['x']) -- nil
print(rawget(test,'x')) -- nil

赋值 test['x'] = 1 将会调用 __newindex 函数。如果 __newindex 函数什么也不做,那就什么也不会发生,我们将得到 test['x'] 的 nil 结果。

如果 __newindex 函数调用了 rawset

local test = {}
setmetatable(test, {
  __newindex = function(t,key,value)
                 rawset(t,key,value) -- t:test key:'x' value:1
               end})
test['x'] = 1
print(test['x']) -- 1
print(rawget(test,'x')) -- 1

这个代码与第一个例子有相同的效果。所以手册上说:

“Lua 不执行原始赋值。(如果需要,元方法本身可以调用 rawset 来执行赋值。)”

那么问题就是,如何使用 __newindex? 它可以用来分离表中的旧索引和新索引。

local test = {y = 1}
local newtest = {}
setmetatable(test, {
    __newindex =
        function(t,key,value)
            newtest[key] = value
        end,
    __index = newtest
})

test["x"] = 1

print(test['x']) -- 1
print(test['y']) -- 1

print(rawget(test, 'x')) -- nil
print(rawget(test, 'y')) -- 1

旧索引 'x' 和新索引 'y' 都可以通过 test[key] 访问,可以通过 rawget(test, key) 分离。

2021-01-29 03:28:51