Lua メタテーブル(Metatable)
Lua のテーブルでは、対応するキーにアクセスして値を取得できますが、2つのテーブルに対して演算(例えば加算)を行うことはできません。
そこで Lua はメタテーブル(Metatable)を提供し、テーブルの動作を変更できるようにしています。各動作には対応するメタメソッドが関連付けられています。
例えば、メタテーブルを使用すると、Lua が2つのテーブルの加算操作 a+b をどのように計算するかを定義できます。
Lua が2つのテーブルを加算しようとするとき、まずどちらかにメタテーブルがあるかを確認し、次に「__add__index というフィールドがあるかどうかを確認します。見つかった場合は、対応する値を呼び出します。__addこのようなフィールドに対応する値(多くの場合、関数またはテーブル)が「メタメソッド」です。
メタテーブルを扱うための非常に重要な関数が2つあります:
- setmetatable(table,metatable):指定されたテーブルにメタテーブルを設定します。メタテーブルの中に __metatable キーが存在する場合、setmetatable は失敗します。
- getmetatable(table):オブジェクトのメタテーブルを返します。
次の例は、指定したテーブルにメタテーブルを設定する方法を示しています:
mymetatable = {} -- メタテーブル
setmetatable(mytable,mymetatable) -- mymetatable を mytable のメタテーブルに設定
上記のコードは1行で書くこともできます:
mytable = setmetatable({},{})
以下はオブジェクトのメタテーブルを返します:
getmetatable(mytable) -- 这会返回 mymetatable
__index メタメソッド
これはメタテーブルで最もよく使われるキーです。
キーを使ってテーブルにアクセスするとき、そのキーに値がない場合、Lua はそのテーブルのメタテーブル(メタテーブルがある場合)にある __index キーを探します。__index がテーブルを含む場合、Lua はそのテーブル内で対応するキーを探します。
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
__index が関数を含む場合、Lua はその関数を呼び出します。テーブルとキーが関数の引数として渡されます。
__index メタメソッドは、テーブル内に要素が存在するかどうかを確認します。存在しない場合は nil を返し、存在する場合は __index が結果を返します。
例
__index = function(mytable, key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
print(mytable.key1,mytable.key2)
例の出力結果は次のとおりです:
value1 metatablevalue
例の解説:
mytable テーブルに代入:{key1 = "value1"}。
mytable にメタテーブルが設定され、メタメソッドは __index です。
mytable テーブル内で key1 を探し、見つかった場合はその要素を返し、見つからない場合は続行します。
mytable テーブル内で key2 を探し、見つかった場合は metatablevalue を返し、見つからない場合は続行します。
メタテーブルに __index メソッドがあるかどうかを判定し、__index メソッドが関数であればその関数を呼び出します。
メタメソッド内で "key2" キーの引数が渡されたかどうかを確認します(mytable.key2 は設定済み)。"key2" 引数が渡された場合は "metatablevalue" を返し、それ以外の場合は mytable の対応するキーの値を返します。
上記のコードは次のように簡潔に書くこともできます:
print(mytable.key1,mytable.key2)
まとめ
Lua がテーブルの要素を探すときの規則は、実に次の3つのステップです:
- 1. テーブル内で探します。見つかった場合はその要素を返し、見つからない場合は続行します。
- 2. そのテーブルにメタテーブルがあるかどうかを判定します。メタテーブルがない場合は nil を返し、ある場合は続行します。
- 3. メタテーブルに __index メソッドがあるかどうかを判定します。__index メソッドが nil の場合は nil を返し、__index メソッドがテーブルの場合は 1、2、3 を繰り返し、__index メソッドが関数の場合はその関数の戻り値を返します。
この部分の内容は作者の寰子によるものです:https://blog.csdn.net/xocoder/article/details/9028347
__newindex メタメソッド
__newindex メタメソッドはテーブルの更新に使われ、__index はテーブルのアクセスに使われます。
テーブルの存在しないインデックスに値を代入しようとすると、インタープリタは __newindex メタメソッドを探します。存在する場合はその関数を呼び出し、代入操作は行いません。
次の例は __newindex メタメソッドの適用を示しています:
例
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })
print(mytable.key1)
mytable.newkey = "新值2"
print(mytable.newkey,mymetatable.newkey)
mytable.key1 = "新值1"
print(mytable.key1,mymetatable.key1)
上記の例を実行した出力結果は次のとおりです:
value1 nil 新值2 新值1 nil
上記の例では、テーブルにメタメソッド __newindex が設定されており、新しいインデックスキー(newkey)に値を代入するとき(mytable.newkey = "新值2")、メタメソッドが呼び出され、代入は行われません。一方、既存のインデックスキー(key1)に代入する場合は、代入が行われ、メタメソッド __newindex は呼び出されません。
次の例では rawset 関数を使ってテーブルを更新しています:
例
__newindex = function(mytable, key, value)
rawset(mytable, key, "\""..value.."\"")
end
})
mytable.key1 = "new value"
mytable.key2 = 4
print(mytable.key1,mytable.key2)
上記の例を実行した出力結果は次のとおりです:
new value "4"
テーブルに演算子を追加する
次の例は2つのテーブルの加算操作を示しています:
例
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
-- 2つのテーブルの加算操作
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
上記の例を実行した出力結果は次のとおりです:
1 1 2 2 3 3 4 4 5 5 6 6
__add キーはメタテーブルに含まれ、加算操作が行われます。テーブル内の対応する演算の一覧は次のとおりです:(注意:__アンダースコア2つです)
| パターン | 説明 |
|---|---|
| __add | 対応する演算子 '+'. |
| __sub | 対応する演算子 '-'. |
| __mul | 対応する演算子 '*'. |
| __div | 対応する演算子 '/'. |
| __mod | 対応する演算子 '%'. |
| __unm | 対応する演算子 '-'. |
| __concat | 対応する演算子 '..'. |
| __eq | 対応する演算子 '=='. |
| __lt | 対応する演算子 '<'. |
| __le | 対応する演算子 '<='. |
__call メタメソッド
__call メタメソッドは、Lua が値を呼び出すときに呼び出されます。次の例は、テーブル内の要素の合計を計算する方法を示しています:
例
-- テーブルの最大キー値を計算する独自関数 table_maxn、つまりテーブルの要素数を計算する
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
-- メタメソッド __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))
上記の例を実行した出力結果は次のとおりです:
70
__tostring メタメソッド
__tostring メタメソッドは、テーブルの出力動作を変更するために使用します。次の例では、テーブルの出力内容をカスタマイズしています:
例
__tostring = function(mytable)
sum = 0
for k, v in pairs(mytable) do
sum = sum + v
end
return "テーブルのすべての要素の合計は " .. sum
end
})
print(mytable)
上記の例を実行した出力結果は次のとおりです:
表所有元素的和为 60
この記事から、メタテーブルがコードの機能をうまく簡素化できることがわかります。Lua のメタテーブルを理解することで、よりシンプルで優れた Lua コードを書くことができるようになります。
その他の拡張