Как регистрировать доступ/запись к вложенным таблицам?

В рамках проекта, над которым я работал, я хотел иметь возможность распечатывать каждый раз, когда к таблице осуществляется доступ или запись, а также то, что было доступно/записано в таблице. Просматривая это, я нашел это, в котором описывалось, как отслеживать, когда таблица доступ/обновление осуществляется с помощью прокси-таблицы и метаметодов __index и __newindex. Однако код, который они предоставляют, не отображает должным образом то, что происходит, если в качестве прокси-таблицы используется вложенная таблица. Предположим, я пишу следующий код, адаптированный из предыдущего:

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 и возвращает значение в прокси-таблице, но у нее нет такой же метатаблицы, поэтому запись в t.c не регистрируется. Я хотел бы, чтобы второе задание напечатало что-то вроде setting key c.a to value 4, но я не совсем уверен, с чего начать на самом деле реализовать такую ​​​​вещь.

После долгих размышлений я думаю, что вы могли бы сделать это, если бы каждый ключ, имеющий значение таблицы, также был другой прокси-таблицей, но тогда вам нужно

  • рекурсивно заменить все значения таблицы прокси-таблицами, и я думал, что каждая прокси-таблица будет включать некоторую информацию, которая позволяет __newindex для этой прокси-таблицы распечатать правильный ключ
  • если для какого-либо ключа установлено значение таблицы, вам необходимо рекурсивно заменить его прокси-таблицами, прежде чем вы сможете установить фактическое значение

и это только кажется, что так много работы и сложности для чего-то, что должно быть проще, чем это.


person hexy    schedule 25.09.2019    source источник


Ответы (1)


Вам нужна прокси-таблица для каждой таблицы, к которой вы хотите получить доступ. Самый простой способ сделать это, хотя и не самый эффективный, — возвращать новую прокси-таблицу всякий раз, когда исходный прокси-сервер получает доступ, и будет возвращена обычная таблица:

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 собирают мусор, создание новых таблиц обычно считается «медленным». Однако это все еще вопрос перспективы; если вы пишете простой скрипт, который запускаете вручную, не утруждайте себя оптимизацией, он все равно будет очень быстрым. Если вы пишете вложенные циклы с миллионами итераций или если ваш код должен отвечать в течение как можно меньшего количества миллисекунд, вам следует рассмотреть возможность кэширования этих прокси-таблиц, в зависимости от вашего варианта использования. Если вы обнаружите, что ваш код снова и снова обращается к одним и тем же прокси-таблицам, создавая каждый раз новые прокси-таблицы, вы можете кэшировать их в таблице прокси, где proxies[table_A] == proxy_to_A и установить метаметод __index, который создает прокси, если его не существует. (На данный момент компромисс заключается в том, что создание новых прокси может быть немного медленнее из-за вызова метаметода).

person DarkWiiPlayer    schedule 26.09.2019
comment
Это приложение предназначено для отладки, поэтому я не возражаю, если оно немного медленное. Это сработало отлично, спасибо! - person hexy; 26.09.2019