Menu
Coddy logo textTech

Метаметод __newindex

Часть раздела Logic & Flow путешествия по 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() во втором примере. Эта функция обходит метатаблицу и напрямую устанавливает значение в таблице.

Без этого установка значения снова вызовет __newindex, создавая бесконечный цикл.

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 (string): Начальный ключ для добавления в таблицу
  • value (string): Начальное значение для добавления в таблицу

Возвращает: Таблицу с валидацией, которая принимает только строковые значения, содержащую начальную пару ключ-значение (table)

Шпаргалка

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

Функция __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(t, key, value), чтобы обойти метатаблицу и напрямую установить значения в таблице. Это предотвращает бесконечные циклы при установке значений внутри __newindex.

Попробуйте сами

function createValidatedTable(key, value)
    -- Напишите код здесь
end
quiz iconПроверьте себя

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

Все уроки раздела Logic & Flow