如何记录嵌套tableaccesses/writes?

How to log nested table accesses/writes?

作为我正在处理的项目的一部分,我希望能够在访问或写入 table 时打印出来,以及 accessed/written 在 [=38 中的内容=].查找此内容,我发现 this,它描述了如何通过使用代理 table 以及 __index 和 [=14 来跟踪何时 table 是 accessed/updated =] 元方法。但是,如果将嵌套 table 用作代理 table,则它们提供的代码无法正确显示正在发生的情况。假设我编写了以下代码,改编自上一个代码:

mt = {}
function mt.__index(self, key)
  print('accessing key '..key)
  return self.proxy[key]
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 -- prints "setting key a to value 2" as expected
t.c.a = 4 -- prints "accessing key c", nothing else

这里的问题是 __index 被调用来访问密钥 c 并且它 returns 代理 table 中的一个值,但是没有相同的 metatable,因此它不会记录对 t.c 的写入。我希望第二个作业打印类似 setting key c.a to value 4 的内容,但我不确定从哪里开始实际实现这样的事情。

经过深思熟虑,我认为您可以通过让每个具有 table 值的键也成为另一个代理 table 来做到这一点,但是您必须

对于本应比这更简单的事情来说,这似乎是太多的工作和复杂的事情。

您需要的是 每个 table 您想要访问的代理 table。执行此操作的最简单方法(虽然不是最高性能)是在访问原始代理时返回一个新的代理-table,并且将返回一个正常的 table:

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 -- Works as expected
t.c.a = 4 -- Also works as expected

性能说明:

由于 Lua 中的 table 被垃圾收集,因此创建新的 table 通常被认为是 "slow"。不过,这仍然是一个观点问题。如果您正在手动编写 运行 的简单脚本,请不要费心优化,它仍然会非常快。如果您正在编写具有数百万次迭代的嵌套循环,或者如果您的代码需要在尽可能短的毫秒内做出响应,那么您应该考虑缓存这些代理 tables, 取决于您的用例。如果您发现您的代码一次又一次地访问相同的代理 tables,每次都创建新的代理-tables,您可以将它们缓存在 proxies table,其中 proxies[table_A] == proxy_to_A 并设置一个 __index 元方法,如果代理不存在则创建该代理。 (此时的权衡是,由于元方法调用,新代理的创建可能会稍微慢一些)。