0Pricing
Lua Academy · Урок

__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 — локальная установка не требуется.

Все уроки этого курса

  1. Введение в метатаблицы
  2. __index и __newindex
  3. Арифметические метаметоды
  4. __tostring и __len
← Назад к Lua Academy