Menu
CoddyTech

Метаметод __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.

challenge icon

Задание

Легко

Напишите функцию 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
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Логика и управление потоком

Потренируйтесь самостоятельно: Онлайн-компилятор Lua