The meta tables provided in Lua are used to help Lua data variables perform personalized behaviors in certain non-predefined functions, such as the addition of two tables. Assuming that a and b are both tables, the meta table can define how to calculate the expression a+b. When Lua tries to add two tables, it will first check whether one of the two has a metatable, and then check whether there is a __add field in the metatable, and if so, the corresponding value of the field is called. This value is called a "meta-method", and this function is used to calculate the sum of tables.
Each value in Lua has a meta table. table and userdata can have their own independent metatables, while values of other data types share a single metatable to which their type belongs. By default, tables have no metatables when they are created, such as:
t = {}
print(getmetatable(t)) --Output is nil
Here we can use the setmetatable function to set or modify the metatable of any table.
t1 = {}
setmetatable(t,t1)
assert(getmetatable(t) == t1)
Any table can be used as a metatable of any value, and a set of related tables can also share a common metatable, which will describe their common behavior. A table can even serve as its own metatable to describe its unique behavior. In Lua code, only table metatables can be set. If you want to set metatables of other types of values, it must be done through C code.
1. Meta-methods of arithmetic classes:
In the following example code, a table will be used to represent the set, and some functions are used to calculate the union and intersection of the set, etc.
Set = {}
local metatable = {} --meta table
--Create a new collection based on the values in the parameter list
function (l)
local set = {}
--Specify all meta tables of collections created by this method to metatable
setmetatable(set,metatable)
for _, v in ipairs(l) do
set[v] = true
end
return set
end
--Get two functions that combine sets
function (a,b)
local res = {}
for k in pairs(a) do
res[k] = true
end
for k in pairs(b) do
res[k] = true
end
return res
end
--Get the function that intersects two sets
function (a,b)
local res = {}
for k in pairs(a) do
res[k] = b[k]
end
return res
end
function (set)
local l = {}
for e in pairs(set) do
l[#l + 1] = e
end
return "{" .. (l,", ") .. "}";
end
function (s)
print((s))
end
-- Finally add the meta method to the meta table, so that two sets created by the method are performed
--When adding the operation, it will be redirected to the method, and the multiplication operation will be redirected to
metatable.__add =
metatable.__mul =
--The following is the test code
s1 = {10,20,30,50}
s2 = {30,1}
s3 = s1 + s2
(s3)
(s3 * s1)
--The output result is:
--{1, 30, 10, 50, 20}
--{30, 10, 50, 20}
In the meta table, each arithmetic operator has corresponding field names. In addition to the above __add (add) and __mul (multiple), there are also __sub (sub), __div (division), __unm (opposite number), __mod (modulo) and __pow (multiple power). In addition, the __concat field can be defined to describe the behavior of the connection operator.
For the example code above, we use operands of type table on both sides of the arithmetic operator. So if it is s1 = s1 + 8, will Lua still work normally? The answer is yes, because the step of Lua in positioning the meta table is that if the first value has a meta table and there is a __add field, then Lua will take this field as a meta method. Otherwise, it will check whether the second value has a meta table and contains a __add field. If so, use this field as a meta method. Finally, if neither value has a meta method, Lua raises an error. However, for the function in the above example, if you execute s1 = s1 + 8, an error will be raised because 8 is not a table object and you cannot execute pairs method calls based on it. In order to obtain more accurate error information, we need to make the following modifications to the function, such as:
function (a,b)
if getmetatable(a) ~= metatable or getmetatable(b) ~= metatable then
error("attempt to 'add' a set with a non-set value")
end
--The following code is the same as the above example.
... ...
end
2. Meta-methods of relationship classes:
The meta table can also specify the meaning of the relational operators. The meta methods are __eq (equal to), __lt (less than) and __le (less than or equal to). As for the other three relational operators, Lua does not provide relevant meta methods, which can be obtained by inversion of the previous three relational operators. See the following example:
Set = {}
local metatable = {}
function (l)
local set = {}
setmetatable(set,metatable)
for _, v in ipairs(l) do
set[v] = true
end
return set
end
metatable.__le = function(a,b)
for k in pairs(a) do
if not b[k] then return false end
end
return true
end
metatable.__lt = function(a,b) return a <= b and not (b <= a) end
metatable.__eq = function(a,b) return a <= b and b <= a end
--The following is the test code:
s1 = {2,4}
s2 = {4,10,2}
print(s1 <= s2) --true
print(s1 < s2) --true
print(s1 >= s1) --true
print(s1 > s1) --false
Unlike the meta-methods of arithmetic classes, meta-methods of relational classes cannot be applied to mixed types.
3. Meta-methods defined by the library:
In addition to the above operator-based meta methods, Lua also provides some framework-based meta methods, such as the print function always calls tostring to format its output. If the current object has a __tostring meta method, tostring will use the return value of the meta method as its return value, such as:
Set = {}
local metatable = {}
function (l)
local set = {}
setmetatable(set,metatable)
for _, v in ipairs(l) do
set[v] = true
end
return set
end
function (set)
local l = {}
for e in pairs(set) do
l[#l + 1] = e
end
return "{" .. (l,", ") .. "}";
end
metatable.__tostring =
--The following is the test code:
s1 = {4,5,10}
print(s1) --{5,10,4}
The functions setmetatable and getmetatable will also use a field (__metatable) in the meta table to protect the meta table, such as:
mt.__metatable = "not your business"
s1 = {}
print(getmetatable(s1)) --Then "not your business" will be printed
setmetatable(s1,{}) --The error message will be output: "cannot change protected metatatable"
From the output results of the above code, it can be seen that once the __metatable field is set, getmetatable will return the value of this field, and setmetatable will raise an error.
4. Meta-methods of table access:
The meta-methods of arithmetic and relational operators define behavior for various error cases, and they do not change the regular behavior of the language. But Lua also provides a way to change the behavior of tables. There are two types of table behaviors that can be changed: querying the table and modifying fields that do not exist in the table.
1). __index meta method:
When accessing a field that does not exist in the table, the result is nil. If we define the meta method __index for the table, the result of that access will be determined by the method. See the following example code:
Window = {}
= {x = 0, y = 0, width = 100, height = 100}
= {} --The meta table of Window
function (o)
setmetatable(o,)
return o
end
--Point Window's meta method __index to an anonymous function
--The parameters table and key of the anonymous function are taken from.
.__index = function(table,key) return [key] end
--The following is the test code:
w = {x = 10, y = 20}
print() --Output 100
print(w.width1) -- Since this field does not exist in the variable, nil is returned.
Finally, Lua provides a more concise representation for the __index element method, such as: .__index = . This method is equivalent to the anonymous function representation method in the above example. In comparison, this concise method is more efficient in execution, but the function method is more extensible.
If you want to disable the __index meta method when accessing the table, you can do it through the function rawget(table,key). This method will not speed up the access efficiency of the table.
2). __newindex meta method:
Unlike __index, this meta method is used for assignments where no key exists, while the former is used for access. When assigning a value to an index that does not exist in a table, the interpreter will look for the __newindex meta method. Call it if there is, instead of directly assigning it. If this meta method points to a table, Lua will assign a value to the table instead of the original table. In addition, like __index, Lua also provides a function rawset(table, key, value) that avoids meta methods and directly operates the current table, and its function is similar to rawget(table, key).
3). Table with default values:
By default, the default value of the field of the table is nil. But we can modify this default value through meta tables, such as:
function setDefault(table,default)
local mt = {__index = function() return default end }
setmetatable(table,mt)
end
tab = {x = 10, y = 20}
print(,) --10 nil
setDefault(tab,0)
print(,) --10 0
4). Track the access of the table:
Both __index and __newindex work only when there is no index to access in the table. Therefore, in order to monitor the access status of a certain table, we can provide it with an empty table as a proxy, and then redirect the __index and __newindex meta methods to the original table, see the following code:
t = {} --The original table
local _t = t --Keep private access to the original table.
t = {} --Create a proxy
--Create meta table
local mt = {
__index = function(table,key)
print("access to element " .. tostring(key))
return _t[key] --Return field value by accessing the original table
end,
__newindex = function(table,key,value)
print("update of element " .. tostring(key) .. " to " .. tostring(value))
_t[key] = value --Update the original table
end
}
setmetatable(t,mt)
t[2] = "hello"
print(t[2])
--The output result is
--update of element 2 to hello
--access to element 2
--hello
5). Read-only table:
Through the concept of proxy, read-only tables can be easily implemented. Just keep track of all update operations on the table and throw an error, see the following example code:
function readOnly(t)
local proxy = {}
local mt = {
__index = t,
__newindex = function(t,k,v)
error("attempt to update a read-only table")
end
}
setmetatable(proxy,mt)
return proxy
end
days = readOnly{"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}
print(days[1])
days[2] = "Noday"
--The output result is:
--[[
Sunday
lua: d:/:6: attempt to update a read-only table
stack traceback:
[C]: in function 'error'
d:/:6: in function <d:/:5>
d:/:15: in main chunk
[C]: ?
]]--