Метаметод __newindex
Часть раздела Логика и управление потоком путешествия по Lua на Coddy. Урок 19 из 54.
Ты узнал, как __index управляет тем, что происходит, когда ты пытаешься прочитать отсутствующий в таблице ключ. Теперь ты познакомишься с его аналогом — __newindex, который управляет тем, что происходит, когда ты присваиваешь значение ключу, которого в таблице ещё нет.
Метаметод __newindex вызывается всякий раз, когда вы пытаетесь присвоить значение ключу, которого в данный момент нет в таблице. Это позволяет перехватывать и контролировать изменения до того, как они произойдут.
Вот простой пример, который не позволяет добавлять в таблицу новые ключи:
local readOnly = {}
local meta = {
__newindex = function(table, key, value)
print("Cannot add key: " .. key)
end
}
setmetatable(readOnly, meta)
readOnly.name = "Alice" -- Вывод: Cannot add key: name
print(readOnly.name) -- Вывод: nilКогда вы пытаетесь установить readOnly.name = "Alice", Lua видит, что в таблице нет name. Вместо добавления этого элемента Lua вызывает функцию __newindex с тремя аргументами: самой таблицей, устанавливаемым ключом и присваиваемым значением.
В этом примере функция просто выводит сообщение и ничего на самом деле не сохраняет.
Вы также можете использовать __newindex, чтобы проверять данные перед добавлением их в таблицу:
local player = {}
local meta = {
__newindex = function(t, key, value)
if key == "health" and type(value) ~= "number" then
print("Health must be a number!")
else
rawset(t, key, value)
end
end
}
setmetatable(player, meta)
player.health = "high" -- Вывод: Health must be a number!
player.health = 100 -- Допустимо, сохраняется с помощью rawset
player.health = "high" -- Сообщения нет: health теперь существуетОбрати внимание на использование rawset() во втором примере. Эта функция обходит метатаблицу и напрямую задаёт значение в таблице.
Без этого установка значения снова вызвала бы __newindex, создав бесконечный цикл.
Поскольку __newindex запускается только для ключей, которых нет в таблице, эта проверка защищает от некорректного первого присваивания каждому ключу. После того как rawset сохранила health, последующие присваивания изменяют его напрямую. Чтобы проверять каждое присваивание, оставь саму таблицу пустой и храни значения во второй таблице, к которой она обращается через __index:
local data = {}
local player = setmetatable({}, {
__index = data,
__newindex = function(t, key, value)
if key == "health" and type(value) ~= "number" then
print("Health must be a number!")
else
data[key] = value
end
end
})
player.health = 100
player.health = "high" -- Вывод: Health must be a number!
print(player.health) -- Вывод: 100У player никогда не появляется собственного ключа, поэтому каждое присваивание проходит через __newindex, а при каждом чтении обращение через __index переходит к data.
Задание
ЛегкоНапишите функцию createValidatedTable, которая принимает key и value и возвращает таблицу, принимающую для новых ключей только строковые значения.
Создайте пустую таблицу и прикрепите к ней метатаблицу с __newindex, которая проверяет тип значения. Если кто-то попытается добавить значение нестрокового типа, используйте rawset(), чтобы вместо него сохранить строку "invalid". Строковые значения сохраняйте обычным образом с помощью rawset().
Логика:
- Создайте пустую таблицу
- Создайте метатаблицу с
__newindex, которая получает таблицу, ключ и значение - Внутри
__newindexпроверьте, является ли значение строкой, используяtype(value) == "string" - Если это строка, используйте
rawset(t, key, value), чтобы сохранить её - Если это не строка, используйте
rawset(t, key, "invalid"), чтобы вместо неё сохранить"invalid" - Прикрепите метатаблицу к таблице с помощью
setmetatable() - Используйте
rawset(), чтобы добавить в таблицу начальную пару ключ-значение (в обход метатаблицы) - Верните таблицу
Параметры:
key(строка): начальный ключ, который нужно добавить в таблицуvalue(строка): начальное значение, которое нужно добавить в таблицу
Возвращаемое значение: таблица с проверкой, принимающая только строковые значения и содержащая начальную пару ключ-значение (таблица)
Попробуйте сами
function createValidatedTable(key, value)
-- Напишите здесь код
end
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Логика и управление потоком
1Продвинутая итерация таблиц
Итерация с помощью pairs()Итерация с помощью ipairs()pairs() против ipairs()Повторение — Лист персонажа2Дополнительные функции библиотеки table
table.concat()Создание таблиц и unpack()table.sort()Пользовательская сортировка с помощью функцийИтоги — Таблица рекордовПотренируйтесь самостоятельно: Онлайн-компилятор Lua