如何记录嵌套表的访问/写入?

作为我正在工作的项目的一部分,我想能够在表被访问或写入时打印出来,以及访问/写入表中的内容。查找了一下,我发现了 这个,其中描述了如何使用代理表和 __index__newindex 元方法跟踪表何时被访问/更新。然而,他们提供的代码如果使用嵌套表作为代理表,则不能正确显示正在发生的情况。假设我写以下代码,根据上一个进行调整:

mt = {}
function mt.__index(self, key)
  print('正在访问键 '..key)
  return self.proxy[key]
end
function mt.__newindex(self, key, value)
  print('将键 '..key..' 设置为值 '..tostring(value))
  self.proxy[key] = value
end

function setproxy(t)
  new_t = {proxy = t}
  setmetatable(new_t, mt)
  return new_t
end

t = {
  a = 1,
  b = 2,
  c = {
    a = 3,
    b = 4,
  },
}

t = setproxy(t)
t.a = 2 -- 正如预期地打印出“将键 a 设置为值 2”
t.c.a = 4 -- 打印“访问键 c”,没有其他输出

问题在于 __index 被调用以访问键 c,并返回代理表中的一个值,但是它没有相同的元表,因此它不会记录对 t.c 的写入。我想要第二个赋值语句打印出类似于“将键 c.a 设置为值 4”的内容,但是我不知道从哪里开始实际实现这样的事情。

经过许多思考,我认为你可以通过使具有表值的每个键也是另一个代理表来实现这一点,但是然后你必须

  • 递归地用代理表替换所有表值,我正在考虑每个代理表是否包含一些信息,以允许 这个代理表的 __newindex 打印出正确的键
  • 如果某个键设置为表值,则必须递归地将其替换为 代理表,然后才能设置实际值

这似乎只是为了某些应该比这更简单的事情而进行的大量工作和复杂性。

点赞
用户4984564
用户4984564

你需要为你想要访问的每个表创建一个代理表。最简单的方法是,每当原始代理被访问时返回一个新的代理表,同时返回一个普通表:

mt = {}
function mt.__index(self, key)
  print('accessing key '..key)
  local value = self.proxy[key]
  if type(value)=='table' then
    return setmetatable({proxy=value}, mt)
  else
    return value
  end
end
function mt.__newindex(self, key, value)
  print('setting key '..key..' to value '..tostring(value))
  self.proxy[key] = value
end

function setproxy(t)
  new_t = {proxy = t}
  setmetatable(new_t, mt)
  return new_t
end

t = {
  a = 1,
  b = 2,
  c = {
    a = 3,
    b = 4,
  },
}

t = setproxy(t)
t.a = 2 --按预期工作
t.c.a = 4 --同样按预期工作

关于性能的说明:

由于Lua中的表是进行垃圾回收的,创建新表通常被认为是“慢”的。不过这仍然是一种相对性观点;如果你正在编写一个简单的脚本,用于手动运行,不必优化,它仍然会非常快。如果你正在编写嵌套循环,有数百万次迭代,或者你的代码需要尽可能在几毫秒内响应,则应该考虑根据你的用例缓存这些代理表。如果你发现你的代码不断地访问相同的代理表,每次都创建新的代理表,则可以将它们缓存在一个_proxies_表中,其中proxies[table_A] == proxy_to_A,并设置一个__index元方法,如果代理不存在,则创建代理(此时,权衡是由于元方法调用创建新代理可能会稍微慢一些)。

2019-09-26 06:57:31