Мне нужны пояснения по Metatable .__ index

Я спросил ранее, почему мои методы для метатаблицы не были обнаружены Lua, и мне сказали, что, установив __index в мою метатаблицу, это решит проблему, поэтому я предположил, что метод при вызове выполнял поиск по индексу в метатаблице , но теперь я столкнулся с проблемой, когда мне нужно использовать скобки для индексации [ и ] в моей метатаблице, поэтому __index назначается для возврата индекса из таблицы внутри нее, как мне решить функциональные потребности обоих с помощью методов, и использование индексных скобок

Я написал минимальный пример, указывающий на проблему:

TestMetatable = {DataTable = {}}
TestMetatable.__index = TestMetatable

function TestMetatable.new()
    local Tmp = {}
    setmetatable(Tmp,TestMetatable)

    Tmp.DataTable = {1}

    return Tmp
end

function TestMetatable:TestMethod()
    print("Ran Successfully")
end

function TestMetatable.__index(self,index)
    return self.DataTable[index]
end

local Test = TestMetatable.new()

-- both functionalities are needed
print(Test[1])
Test:TestMethod()

person Weeve Ferrelaine    schedule 02.08.2013    source источник


Ответы (2)


Вам необходимо понимать разницу между __index и __newindex и их взаимосвязь с текущим содержимым основной таблицы.

__newindex вызывается / осуществляется доступ только в том случае, если выполняются все следующие условия:

  • Когда вы устанавливаете значение в основную таблицу с помощью tbl[index] = expr (или аналогичного синтаксиса, например tbl.name = expr).
  • Когда ключ, который вы пытаетесь установить в основную таблицу, еще не существует в основной таблице.

Второй часто сбивает людей с толку. И в этом ваша проблема, потому что __index только доступен, когда:

  • Когда ключ, считываемый из основной таблицы, еще не существует в основной таблице.

Поэтому, если вы хотите отфильтровать каждое чтение из таблицы и запись в нее, эта таблица всегда должна быть пустой. Следовательно, эти операции чтения и записи должны попадать в какую-то другую таблицу, которую вы создаете для каждого нового объекта. Итак, ваша функция new должна создать две таблицы: одну, которая остается пустой, и другую, в которой хранятся все данные.

Честно говоря, мне бы хотелось, чтобы у Lua был способ создать просто пустую часть пользовательских данных, к которой вы могли бы привязать определяемую пользователем метатаблицу, просто чтобы избежать этих проблем.

person Nicol Bolas    schedule 02.08.2013
comment
Я понял __newindex, но я не понимаю, как это применимо к моей проблеме, после прочтения вашего сообщения ~ 4 раза подряд кажется, что вы предлагаете пустую метатаблицу со всеми функциями, хранящимися в таблице внутри объекта и данные в отдельной таблице с использованием __newindex в качестве переключателя дорожек, чтобы решить, к какой из двух таблиц осуществляется доступ при индексировании метатаблицы? Разве я не мог просто написать код в __index и / или __newindex, который будет переключателем дорожек для функций в метатаблице и данных в таблице в метатаблице? - person Weeve Ferrelaine; 03.08.2013
comment
Моя путаница заключается в том, как определить, когда метатаблица индексируется для функции или числа, поскольку число является единственным допустимым типом для доступа к таблице данных (в частности, я пишу класс изображения) - person Weeve Ferrelaine; 03.08.2013
comment
Раньше в Lua была функция proxy, которая создавала ту часть пользовательских данных, о которой вы говорите. Его легко добавить самостоятельно. - person lhf; 03.08.2013
comment
@JohnDoe: Я не понимаю, как это применимо к моей проблеме, потому что __index имеет такое же ограничение, что и __newindex. Это та часть, где я сказал: «И в этом ваша проблема…» Если вы хотите отфильтровать доступ к таблице, вы должны сделать то, что я сказал. Если в вашей основной таблице есть члены непосредственно в ней, ваш __index метаметод никогда не будет вызываться для доступа к ним. - person Nicol Bolas; 03.08.2013
comment
@lhf: Это легко добавить самому. Конечно, из C API. Но если вы пишете сценарий на чистом Lua, это практически невозможно. Вот почему это должна быть простая стандартная библиотека, чтобы все скрипты имели к ней доступ. - person Nicol Bolas; 03.08.2013
comment
Теперь я вас понял. Посмотрите на ответ, который я опубликовал ниже, он неожиданно сработал, и мне не нужно было иметь метатабильный объект пустым, спасибо за помощь ‹3 - person Weeve Ferrelaine; 03.08.2013
comment
Я подозреваю, что он не был брошен в рекурсию, потому что сама метатаблица не вызывает __index, а только объекты, созданные из нее, это правда? - person Weeve Ferrelaine; 03.08.2013

способ, которым я решил эту проблему, согласно решению Николая Боласа, если это может внести ясность в чью-либо путаницу :-)

TestMetatable = {DataTable = {}, FunctionTable = {}}

function TestMetatable.new()
    local Tmp = {}
    setmetatable(Tmp,TestMetatable)

    Tmp.DataTable = {1}
    Tmp.FunctionTable = TestMetatable

    return Tmp
end

function TestMetatable:TestMethod()
    print("Ran Successfully")
end

function TestMetatable.__index(self,index)
    if type(index) == "string" then
        return self.FunctionTable[index]
    else
        return self.DataTable[index]
    end
end

local Test = TestMetatable.new()

-- both functionalities are needed
print(Test[1])
Test:TestMethod()
person Weeve Ferrelaine    schedule 02.08.2013
comment
Если только кто-то не сделает Test.TestMethod = anything, и тогда ваш код сломается. - person Nicol Bolas; 03.08.2013
comment
true, но разве Test.TestMethod не вернет значение функции в метатаблице, и тогда для функции в метатаблице будет установлено что-нибудь? независимо от того, мне никогда не нужно будет изменять функциональность класса после его создания (это против oop), но я мог видеть, как некоторые могут, так что спасибо за указание на это - person Weeve Ferrelaine; 03.08.2013
comment
Нет. tbl.MemberName = X вызовет __newindex, а не __index, чтобы установить X в поле tbl MemberName. И снова, только если MemberName не имеет значения в таблице. Вот почему важно использовать и то, и другое, и чтобы таблица оставалась пустой, если вы хотите контролировать, где хранятся данные. - person Nicol Bolas; 03.08.2013
comment
но не будет ли Test.TestMethod = X получить доступ к чему-то, что уже существует в Test (потому что все функции объявлены в области метатаблицы), даже если они не являются теми, к которым осуществляется доступ, когда вы его индексируете, я мог видеть, что Test.NewFunction = X выдаст ошибку, однако я протестирую Test.TestMethod и посмотрю :-) - person Weeve Ferrelaine; 03.08.2013
comment
@JohnDoe Может быть полезно подумать об этом, так как __index обрабатывает доступ для чтения, а __newindex обрабатывает доступ для записи в таблицу. Test.TestMethod = anything пытается присвоить какое-то значение таблице Test, поэтому здесь используется доступ для записи, поэтому __newindex. - person greatwolf; 03.08.2013
comment
но доступ на запись к уже существующему индексу? __newindex все еще используется для этого? Я объяснил, что __newindex используется только для создания несуществующих индексов. - person Weeve Ferrelaine; 03.08.2013
comment
@JohnDoe в таком случае нет. Помните, что это резервные обработчики. Lua обращается к ним только в том случае, если ключ не уже существует в таблице. Шаблон прокси, объясненный Никола и в PiL, работает, потому что вы начинаете с пустой таблицы и оставляете ее пустой. Вы делаете это, перехватывая все операции чтения и записи в этой таблице через __index и __newindex. - person greatwolf; 03.08.2013
comment
любые изменения в функции требуют нарушения oop, поэтому эта функциональность не является несуществующей и неприменимой, поскольку целью метатаблиц было имитировать oop - person Weeve Ferrelaine; 03.08.2013
comment
__index, похоже, не работает как резервный оператор, на практике он называл мой __index, даже когда TestMetatable.TestMethod был действительным ключом - person Weeve Ferrelaine; 03.08.2013
comment
Это потому, что __index никогда не присваивает значение этому ключу. После вызова TestMetable.TestMethod по-прежнему не является действительным ключом в вашей таблице, потому что он все еще nil. В тот момент, когда вы сделаете его отличным от нуля, назначив ему что-то, __index больше не будет вызываться для TestMetable.TestMethod. Опять же, вы, конечно, можете перехватить это назначение с помощью __newindex и обработать его по-другому. - person greatwolf; 03.08.2013
comment
запустите опубликованный мной код, он работает правильно, индекс TestMetatable.TestMethod не равен нулю, потому что для него был вызван __index, а не __newindex, пусть он печатает при его вызове, это меня тоже шокировало - person Weeve Ferrelaine; 03.08.2013
comment
@JohnDoe Так как же узнать, есть ли у таблицы ключ или нет? Просто итератор по таблице и выгрузите пары "ключ-значение". for k, v in pairs(t) do print(k, v) end. Так вы точно узнаете, есть ли у таблицы ключ. Вы обнаружите, что доступ к любой из этих клавиш никогда не запускает __index. - person greatwolf; 03.08.2013
comment
@JohnDoe: Вы все упускаете из виду. Дело в том, что вы можете изменить значение TestMethod. Просто вставьте Test.TestMethod = 4 перед его вызовом и посмотрите, будет ли вызван метод. - person Nicol Bolas; 03.08.2013
comment
Я на своей машине с Windows, не могу прямо сейчас, я проверю утром и, возможно, отредактирую свой пост, если он не - person Weeve Ferrelaine; 03.08.2013
comment
Работал, как ожидалось, я попытался вызвать числовое значение, как ожидалось, а не попытался вызвать нулевое значение. - person Weeve Ferrelaine; 03.08.2013