Lua Metatable

In a Lua table, we can access the corresponding key to get the value, but we cannot perform operations on two tables (such as addition).

Therefore Lua provides metatables, allowing us to change the behavior of tables. Each behavior is associated with a corresponding metamethod.

For example, using metatables we can define how Lua calculates the addition operation a+b of two tables.

When Lua tries to add two tables, it first checks whether either of them has a metatable, then checks whether there is a field called__addIf found, the corresponding value is called.__addFields such as these; their corresponding values (often a function or a table) are the "metamethods".

There are two very important functions for handling metatables:

  • setmetatable(table,metatable):Sets the metatable for the specified table. If the metatable contains a __metatable key, 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 metatable of an object:

getmetatable(mytable)                 -- 这会返回 mymetatable

__index metamethod

This is the most commonly used key in a metatable.

When you access a table by key, if the 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 looks up the corresponding key in that table.

We can use the lua command to enter interactive mode and check:

$ 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, passing the table and the key as arguments to the function.

The __index metamethod checks whether an element exists in the table. If it does not exist, the result returned is nil; if it exists, the result is returned by __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 the example is:

value1    metatablevalue

Example analysis:

  • The mytable table is assigned as{key1 = "value1"}。

  • mytable has a metatable set, and the metamethod is __index.

  • Look for key1 in the mytable table. If found, return the element; if not found, continue.

  • Look for key2 in the mytable table. If found, return metatablevalue; if not found, continue.

  • Determine whether the metatable has an __index method. If the __index method is a function, call that function.

  • In the metamethod, check whether the "key2" key parameter is passed (mytable.key2 is set). If the "key2" parameter is passed, return "metatablevalue"; otherwise, return the corresponding value of mytable for the key.

We can rewrite the above code simply as:

mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1,mytable.key2)

Summary

The rules when Lua looks up a table element are actually the following 3 steps:

  • 1. Look in the table. If found, return the element; if not found, continue.
  • 2. Determine whether the table has a metatable. If it has no metatable, return nil; if it has a metatable, continue.
  • 3. Determine whether the metatable has an __index method. If the __index method is nil, return nil; if the __index method is a table, repeat steps 1, 2, and 3; if the __index method is a function, return the return value of that function.

This part is from author Huanzi: https://blog.csdn.net/xocoder/article/details/9028347


__newindex metamethod

The __newindex metamethod is used to update tables, while __index is used to access tables.

When you assign a value to a missing index of a table, the interpreter looks for the __newindex metamethod: if it exists, it calls this function without performing the assignment.

The following example demonstrates the application 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 after executing the above example is:

value1
nil    新值2
新值1    nil

In the above example, the table has the metamethod __newindex set. When assigning to a new index key (newkey) (mytable.newkey = "new value 2"), the metamethod is called and the assignment is not performed. When assigning to an existing index key (key1), the assignment is performed and the __newindex metamethod is not called.

The following example uses the rawset function to update a 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 after executing the above example is:

new value    "4"

Adding operators to tables

The following example demonstrates the addition of two tables:

Example

-- Custom function table_maxn to calculate the maximum key value in a table, i.e., return the maximum key value of the table
function table_maxn(t)
    local mn = 0
    for k, _ in pairs(t) do
        if type(k) == "number" and k > mn then
            mn = k
        end
    end
    return mn
end

-- Addition operation of two tables
mytable = setmetatable({ 1, 2, 3 }, {
  __add = function(mytable, newtable)
    local max_key_mytable = table_maxn(mytable)
    for i = 1, table_maxn(newtable) do
      table.insert(mytable, max_key_mytable + i, 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 after executing the above example is:

1    1
2    2
3    3
4    4
5    5
6    6

The __add key is included in the metatable and performs the addition operation. The list of corresponding operations in the table is as follows: (Note:__are two underscores)

PatternDescription
__addCorresponding operator '+'.
__subCorresponding operator '-'.
__mulCorresponding operator '*'.
__divCorresponding operator '/'.
__modCorresponding operator '%'.
__unmCorresponding operator '-'.
__concatCorresponding operator '..'.
__eqCorresponding operator '=='.
__ltCorresponding operator '<'.
__leCorresponding operator '<='.

__call metamethod

The __call metamethod is called when Lua calls a value. The following example demonstrates calculating the sum of elements in a table:

Example

-- Calculate the maximum value in the table; table.maxn cannot be used in Lua versions above 5.2
-- Custom function table_maxn to calculate the maximum key value in a table, i.e., calculate the number of elements in the table
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 metamethod __call
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 after executing the above example is:

70

__tostring metamethod

The __tostring metamethod is used to modify the output behavior of a table. In the following example, we customize the output content 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 "The sum of all elements in the table is " .. sum
  end
})
print(mytable)

The output after executing the above example is:

表所有元素的和为 60

From this article, we can learn that metatables can effectively simplify our code functionality, so understanding Lua's metatables can help us write simpler and better Lua code.

Other extensions