Lua Metatables
In Lua, we can access the corresponding key to get the value in a table, but we cannot perform operations on two tables (such as addition).
Therefore, Lua provides metatables, which allow us to alter the behavior of tables, with each behavior associated with a corresponding metamethod.
For example, using metatables, we can define how Lua computes the addition of two tables, a + b.
When Lua attempts to add two tables, it first checks if either of them has a metatable and then checks if there is a field called __add
. If found, it calls the corresponding value. Fields like __add
are immediate fields, and their corresponding values (often functions or tables) are "metamethods".
There are two important functions for handling metatables:
- setmetatable(table, metatable): Sets the metatable for the specified table. If the metatable contains the key
__metatable
, setmetatable will fail. - getmetatable(table): Returns the metatable of the object.
The following example demonstrates how to set a metatable for a specified table:
mytable = {} -- Ordinary table
mymetatable = {} -- Metatable
setmetatable(mytable, mymetatable) -- Set mymetatable as the metatable of mytable
The above code can also be written in one line:
mytable = setmetatable({}, {})
The following returns the object's metatable:
getmetatable(mytable) -- This will return mymetatable
__index Metamethod
This is the most commonly used key in metatables.
When you access a table by key, if this key has no value, Lua will look for the __index
key in the table's metatable (assuming there is a metatable). If __index
contains a table, Lua will look up the corresponding key in that table.
We can see this in action using the Lua interactive mode:
$ lua
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
> other = { foo = 3 }
> t = setmetatable({}, { __index = other })
> t.foo
3
> t.bar
nil
If __index
contains a function, Lua will call that function, with the table and key as arguments.
The __index
metamethod checks if an element exists in the table. If it does not exist, it returns nil; if it exists, it returns the result from __index
.
Example
mytable = setmetatable({key1 = "value1"}, {
__index = function(mytable, key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
print(mytable.key1, mytable.key2)
The output of this example is:
value1 metatablevalue
Example Analysis:
mytable
is assigned as{key1 = "value1"}
.mytable
is set with a metatable, with the metamethod__index
.- When looking up
key1
inmytable
, if found, it returns the element; if not, it continues. - When looking up
key2
inmytable
, if found, it returnsmetatablevalue
; if not, it continues. - It checks if the metatable has an
__index
method. If__index
is a function, it calls this function. - The metamethod checks if the parameter "key2" is passed (since
mytable.key2
is set). If "key2" is passed, it returns "metatablevalue"; otherwise, it returns the corresponding value frommytable
.
We can simplify the above code to:
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1, mytable.key2)
Summary
The rules Lua uses to look up a table element are as follows:
- Look in the table. If found, return the element; if not, continue.
- Check if the table has a metatable. If no metatable, return nil; if there is a metatable, continue.
- Check if the metatable has an
__index
method. If__index
is nil, return nil; if__index
is a table, repeat steps 1, 2, 3; if__index
is a function, return the function's return value.
This content is from the author Huanzi: https://blog.csdn.net/xocoder/article/details/9028347
__newindex Metamethod
The __newindex
metamethod is used for updating tables, while __index
is used for accessing tables.
When you assign a value to a missing index in a table, the interpreter looks for the __newindex
metamethod: if found, it calls this function instead of performing the assignment.
The following example demonstrates the use of the __newindex
metamethod:
Example
mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })
print(mytable.key1)
mytable.newkey = "New Value 2"
print(mytable.newkey, mymetatable.newkey)
mytable.key1 = "New Value 1"
print(mytable.key1, mymetatable.key1)
The output of this example is:
value1
nil New Value 2
New Value 1 nil
In this example, the table has the __newindex
metamethod set. When assigning to a new index key (mytable.newkey = "New Value 2"
), it calls the metamethod and does not perform the assignment. However, if assigning to an existing index key (key1
), it performs the assignment without calling the __newindex
metamethod.
The following example uses the rawset
function to update the table:
Example
mytable = setmetatable({key1 = "value1"}, {
__newindex = function(mytable, key, value)
rawset(mytable, key, "\"" .. value .. "\"")
end
})
mytable.key1 = "new value"
mytable.key2 = 4
print(mytable.key1, mytable.key2)
The output of this example is:
new value "4"
Adding Operators to Tables
The following example demonstrates adding two tables:
Example
-- Function to calculate the maximum value in a table, as table.maxn is deprecated in Lua 5.2+
function table_maxn(t)
local mn = 0
for k, v in pairs(t) do
if mn < k then
mn = k
end
end
return mn
end
-- Adding two tables
mytable = setmetatable({ 1, 2, 3 }, {
__add = function(mytable, newtable)
for i = 1, table_maxn(newtable) do
table.insert(mytable, table_maxn(mytable) + 1, newtable[i])
end
return mytable
end
})
secondtable = {4, 5, 6}
mytable = mytable + secondtable
for k, v in ipairs(mytable) do
print(k, v)
end
The output of this example is:
1 1
2 2
3 3
4 4
5 5
6 6
The __add
key is included in the metatable for addition operations. The corresponding operations for tables are as follows (Note: __
is two underscores):
Mode | Description |
---|---|
__add | Corresponding operator '+'. |
__sub | Corresponding operator '-'. |
__mul | Corresponding operator '*'. |
__div | Corresponding operator '/'. |
__mod | Corresponding operator '%'. |
__unm | Corresponding operator '-'. |
__concat | Corresponding operator '..'. |
__eq | Corresponding operator '=='. |
__lt | Corresponding operator '<'. |
__le | Corresponding operator '<='. |
__call Metamethod
The __call
metamethod is invoked when Lua calls a value. The following example demonstrates calculating the sum of elements in a table:
Example
-- Function to calculate the maximum value in a table, as table.maxn is deprecated in Lua 5.2+
function table_maxn(t)
local mn = 0
for k, v in pairs(t) do
if mn < k then
mn = k
end
end
return mn
end
-- Define the __call metamethod
mytable = setmetatable({10}, {
__call = function(mytable, newtable)
sum = 0
for i = 1, table_maxn(mytable) do
sum = sum + mytable[i]
end
for i = 1, table_maxn(newtable) do
sum = sum + newtable[i]
end
return sum
end
})
newtable = {10, 20, 30}
print(mytable(newtable))
The output of this example is:
70
__tostring Metamethod
The __tostring
metamethod is used to modify the output behavior of a table. The following example customizes the output of a table:
Example
mytable = setmetatable({ 10, 20, 30 }, {
__tostring = function(mytable)
sum = 0
for k, v in pairs(mytable) do
sum = sum + v
end
return "Sum of table elements is " .. sum
end
})
print(mytable)
The output of this example is:
Sum of table elements is 60
Understanding metatables can greatly simplify our code and allow us to write more elegant Lua code.