__index и __newindex
Перехватывайте чтение и запись полей с помощью метаметодов __index и __newindex.
«__index и __newindex» — бесплатный урок Lua Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Lua Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Lua Academy содержит 4 уроков всего.
Повторение: __index
__index срабатывает, когда Lua пытается прочитать ключ, которого нет в таблице. Это может быть таблица (поиск в прототипе) или функция (динамическое вычисление). Это наиболее часто используемый метаметод в Lua — он обеспечивает наследование, значения по умолчанию и ленивую инициализацию.
local defaults = {timeout=30, retries=3}
local config = setmetatable({timeout=60}, {__index=defaults})
print(config.timeout) -- 60 (own value)
print(config.retries) -- 3 (from defaults)
print(config.missing) -- nil (not in either)Срабатывание __newindex
__newindex срабатывает, когда Lua пытается записать ключ, которого ещё нет в таблице. Если ключ уже существует, присваивание выполняется напрямую, без срабатывания __newindex.
local proxy = setmetatable({}, {
__newindex = function(t, k, v)
print("New key:", k, "=", v)
rawset(t, k, v) -- actually store it
end
})
proxy.x = 10 -- New key: x = 10
proxy.x = 20 -- no trigger! x already exists
print(proxy.x) -- 20Таблицы только для чтения
Используйте __newindex, чтобы создавать таблицы только для чтения. Перехватывайте все записи и вызывайте ошибку. Объедините это с __index, чтобы предоставлять значения, не сохраняя их в самой таблице.
local function readOnly(t)
return setmetatable({}, {
__index = t,
__newindex = function(_, k, _)
error("attempt to write to read-only table key: " .. tostring(k), 2)
end
})
end
local cfg = readOnly({host="localhost", port=8080})
print(cfg.host) -- localhost
-- cfg.host = "x" -- ERROR: attempt to write to read-only tableОтслеживание изменений
Используйте __newindex, чтобы отслеживать все изменения таблицы — это полезно для отладки, журналирования изменений или реактивных привязок данных. Храните данные в скрытой резервной таблице, а изменения записывайте в журнал.
local log = {}
local data = {}
local tracked = setmetatable({}, {
__index = data,
__newindex = function(_, k, v)
log[#log+1] = {key=k, old=data[k], new=v}
data[k] = v
end
})
tracked.name = "Alice"
tracked.age = 30
tracked.name = "Bob"
for _, entry in ipairs(log) do
print(entry.key, entry.old, "->", entry.new)
endОбъекты-прокси
Прокси оборачивает другой объект и перехватывает все операции чтения и записи. Используйте пустую таблицу в качестве прокси, указывая в __index и __newindex функции. Настоящие данные хранятся в отдельной таблице.
local function makeProxy(target)
return setmetatable({}, {
__index = function(_, k) return target[k] end,
__newindex = function(_, k, v)
print("Setting", k, "on target")
target[k] = v
end,
})
end
local real = {x=10}
local p = makeProxy(real)
print(p.x) -- 10 (reads from real)
p.y = 20 -- Setting y on target
print(real.y) -- 20Цепочка __index (наследование)
Цепочки метатаблиц через __index создают наследование на основе прототипов. Если ключ не найден в объекте, Lua проверяет класс, затем родительский класс и так далее. Для каждого уровня нужна собственная метатаблица, в которой __index указывает на следующий уровень.
local Base = {type = "base", version = 1}
local Child = setmetatable({type = "child"}, {__index = Base})
local Obj = setmetatable({}, {__index = Child})
print(Obj.type) -- base? No: child (Child has it)
print(Obj.version) -- 1 (from Base)
print(Obj.type) -- child (Child overrides Base.type)Ленивая инициализация с помощью __index
Используйте функциональную форму __index, чтобы вычислять и кэшировать ресурсоёмкие значения при первом обращении. При первом чтении вычислите значение и сохраните его непосредственно в таблице (это позволит избежать последующих вызовов __index).
local lazy = setmetatable({}, {
__index = function(t, k)
if k == "expensiveData" then
print("Computing...")
local result = {1,2,3,4,5} -- simulate work
rawset(t, k, result)
return result
end
end
})
print(lazy.expensiveData) -- Computing... then table
print(lazy.expensiveData) -- (no "Computing" this time)__newindex для проверки схемы
Используйте __newindex, чтобы обеспечить соблюдение схемы: разрешайте задавать только заранее определённые ключи или значения правильного типа. Это добавляет таблицам Lua лёгкую проверку типов.
local schema = {name="string", age="number", active="boolean"}
local function schemaTable()
local store = {}
return setmetatable({}, {
__index = store,
__newindex = function(_, k, v)
local expected = schema[k]
if not expected then error("unknown key: "..k,2) end
if type(v) ~= expected then
error(k.." must be "..expected..", got "..type(v),2)
end
store[k] = v
end
})
end
local user = schemaTable()
user.name = "Alice"
user.age = 30
-- user.age = "thirty" -- ERROR__index и rawget
Внутри функции __index используйте rawget, чтобы прочитать значение из таблицы и не вызвать __index повторно (что привело бы к бесконечной рекурсии). Внутри метаметодов всегда используйте rawget/rawset для работы с фактическим хранилищем таблицы.
local counter = setmetatable({count=0}, {
__index = function(t, k)
-- Use rawget to avoid recursion
local c = rawget(t, "count")
rawset(t, "count", c + 1)
return rawget(t, k)
end
})
print(counter.count) -- 0 (direct, no __index)
print(counter.missing) -- nil
print(counter.count) -- 1 (incremented)Объединение __index и __newindex
Наиболее мощный шаблон: используйте пустую таблицу-прокси с __index и __newindex, связанную со скрытой таблицей данных. Это даёт полный контроль над чтением и записью и позволяет реализовать обнаружение изменений, ленивую загрузку и контроль доступа.
local function observable(init)
local data = init or {}
local listeners = {}
local obj = setmetatable({}, {
__index = data,
__newindex = function(_, k, v)
local old = data[k]
data[k] = v
for _, cb in ipairs(listeners) do cb(k,old,v) end
end
})
obj._onchange = function(_, cb) listeners[#listeners+1]=cb end
return obj
end
local obs = observable({score=0})
obs:_onchange(function(k,o,n) print(k,o,"->",n) end)
obs.score = 100 -- score 0 -> 100Быстрая проверка
В каком случае __newindex НЕ срабатывает?
Повторение: __index и __newindex
Основные положения:
__index: срабатывает при чтении отсутствующего ключа; таблица или функция__newindex: срабатывает только при записи нового ключа- Используйте
rawget/rawsetвнутри метаметодов, чтобы избежать рекурсии - Шаблоны: таблицы только для чтения, отслеживание изменений, ленивая инициализация, проверка схемы
- Пустая таблица-прокси + резервная таблица = полный перехват чтения и записи
Часто задаваемые вопросы
Урок «__index и __newindex» бесплатный?
Да — полный текст урока «__index и __newindex» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Lua Academy, подпишись на CoddyKit PRO. Курс Lua Academy содержит 4 уроков всего.
Чему я научусь в уроке «__index и __newindex»?
Перехватывайте чтение и запись полей с помощью метаметодов __index и __newindex. Ты практикуешь Lua Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Lua Academy?
Предыдущий опыт не требуется. Lua Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «__index и __newindex»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Lua Academy?
Да. Каждый урок Lua Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Введение в метатаблицы
- __index и __newindex
- Арифметические метаметоды
- __tostring и __len