Lua - __newindex метаметод для существующего индекса?

Недавно я узнал о существовании метатаблиц в Луа, и я играл с ними, пока не пришла в голову мысль: можно ли будет использовать их, чтобы попытаться избежать "дубликатов" в таблице? Я искал и искал и до сих пор не мог найти то, что я ищу, так что я здесь.

  • Итак, вот что я хотел бы сделать, и цель:

Это будет использоваться в программировании дополнений WoW. Я хочу создать инструмент, который выдает предупреждение при создании переменной или функции в глобальной области видимости (чтобы избежать ее использования из-за возможных конфликтов имен, которые могут возникнуть с другими аддонами). Еще одна вещь, которую я мог бы сделать, это перенаправить весь транзит из таблицы _G и обратно. Поэтому, когда пользователь создает переменную или функцию в глобальной области видимости, инструмент отлавливает это, сохраняет его в таблице вместо _G, и всякий раз, когда пользователи пытаются получить доступ к чему-либо из _G, инструмент сначала ищет его в Таблица; и использовать _G только как запасной вариант. Таким образом, пользователь сможет не беспокоиться о правильной инкапсуляции или присвоении имен, инструмент позаботится обо всем за него.

  • Что мне уже удалось сделать:

Я устанавливаю метаметод __newindex на _G, чтобы перехватывать глобальные переменные и функции области действия, и удаляю метаметод в конце загрузки аддона, чтобы избежать его использования другими аддонами. Что касается "косвенности _G транзита", я уже знаю, как я мог бы использовать __index, чтобы попытаться дать значение, хранящееся в другой таблице, вместо того, чтобы пытаться использовать _G.

  • Проблема, которую я имею:

Это хорошо работает, но только с переменными и функциями, которых еще нет в _G. Всякий раз, когда присваивается значение ключу, который уже находится в таблице _G, он не работает (по понятным причинам). Я хотел бы действительно иметь возможность отлавливать эти случаи и, по сути, сделать невозможным перезапись содержимого _G, а вместо этого использовать своего рода "перегрузку" (но без необходимости того, чтобы пользователь даже знал об этом).

  • Что я попробовал:

Я попытался перехватить rawset, чтобы увидеть, был ли он вызван автоматически, и кажется, что это не так.

Я не смог найти много документации о таблице _G в lua, в основном из-за короткого имени. Я уверен, что где-то должно что-то существовать, и я мог бы, вероятно, использовать эту информацию, чтобы все было сделано так, как я хочу, но в настоящее время я просто потерян и исчерпан идеями. Так что да, я хотел бы знать, есть ли какой-нибудь способ "перехватить" все "неявные вызовы rawset", чтобы сделать некоторые проверки, прежде чем позволить ему делать свое дело. Я понял, что, по-видимому, мета-метода для __existingindex или чего-то еще нет, так что вы знаете, как это сделать, пожалуйста?

1 ответ

Хотя у вас есть ответ в комментариях, в Lua 5.1 есть более глубокая концепция окружения. Окружение - это таблица, присоединенная к функции, где эта функция перенаправляет свои "глобальные" операции чтения и записи. _G это просто ссылка на "глобальную" среду, то есть среду основного потока (основной сопрограммы). Его можно очистить до нуля без неочевидных эффектов, потому что это просто переменная, что-то вроде T = { }; T._T = T,

В частности, _G == getfenv(0), если кто-то не изменит его значение (см. ссылку на getfenv() для определения его аргумента). Когда скрипт загружен, он неявно привязан к глобальной среде. Поскольку область верхнего уровня Lua (он же главный блок) - это просто анонимная функция, ее окружение можно в любой момент восстановить в любой другой таблице:

-- x.lua

local T = { }
local shadow = setmetatable({ }, { __index = getfenv(0) })

local mt = {
    __index = shadow,
    __newindex = function (t, k, v)
        -- while T is empty, this will be called every time you 'set global'
        -- do whatever you want here
        shadow[k] = v
        print(tostring(k)..' is set to '..tostring(v))
    end
}

setmetatable(T, mt) -- T[k] goes to shadow, then to _G
setfenv(1, T)       -- change the environment of this module to T

hello = "World"     -- 'hello is set to World'
print(T.hello)      -- 'World'
print(_G.hello)     -- 'nil', because we didn't even touch _G

hello = 3.14        -- 'hello is set to 3.14'
hello = nil         -- 'hello is set to nil'
hello = 2.72        -- 'hello is set to 2.72'

function f()        -- 'f is set to function: 0x804a00'
    print(hello)
end

f()                 -- '2.72'

assert(getfenv(f) == getfenv(1))
assert(getfenv(f) == T)

setfenv(f, _G)      -- return f back to _G
f()                 -- 'nil'

При таком подходе вы можете полностью скрыть метатабельную механику от других модулей. Обратите внимание, что изменения в mt не иметь никакого эффекта после setmetatable() вызов.

Также помните, что все функции, определенные ниже setfenv() жить в одной среде T (это не относится к внешним функциям / модулям, загруженным через require или возвращается из этих функций / модулей, потому что наследование среды является лексическим).

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

Другие вопросы по тегам