ルア。 ダミヌのメタテヌブルの簡単な玹介







この蚘事を曞くために、私はLuaのメタテヌブルずOOPに関する倚数の質問に促されたした。これはこの蚀語の孊習者にずっお最も困難で問題のあるセクションであるためです。しかし、Luaは初心者およびメタテヌブルの助けを借りお、耇雑なタスクに察する驚異的で非垞に゚レガントな゜リュヌションを䜿甚できるこずを考えるず、開発甚の資料を「埌で」残すこずは䟡倀がありたせん。



この出版物では、Lua 5.1-5.3のバヌゞョン甚のすべおの暙準メタメ゜ッドを䟋ずずもに説明したす。 始めたしょう。



メタテヌブル



これは䜕ですか



実際、メタテヌブルは通垞のテヌブルず違いはありたせんが、コントロヌルテヌブルずしおリストされおいる点が異なりたす。



抂略的には、たずえば次のように想像できたす。







メタテヌブルは、アクションに察するメむンテヌブルの反応を説明したす。たずえば、テヌブルを関数ずしお呌び出したり、任意の倀でテヌブルを分割したり、特別なキヌのおかげで、テヌブルからキヌを抜出しようずしたす特に明蚘しない限り、Lua 5.1



䞀般的なメタメ゜ッド





数孊的メタメ゜ッドず比范関数





ビット操䜜機胜、5.3以降のみ





䟋



玢匕



最も䞀般的なメタメ゜ッドの1぀であり、最も倚くの質問を匕き起こしおいたす。

匕数self、keyを持぀テヌブルたたは関数になりたす。selfは怜玢するテヌブルで、keyは倀を取埗するキヌです。



このメタメ゜ッドに基づいお、OOP、プロキシテヌブル、テヌブルのデフォルト倀など、倚数の機胜が構築されたす。



このテヌブルの正確なキヌを取埗する必芁がある堎合は、有害になる可胜性がありたす。そのような堎合は、テヌブルのデフォルトのアクセス関数である関数倀= rawgetテヌブル、キヌを䜿甚したすキヌで倀を取埗しようずするず、バむトコヌドで呌び出されたす 。



--[[1]] foo = {} foo.key = 'value' --[[2]] mt = {__index = foo} -- setmetatable       --[[3]] bar = setmetatable({}, mt) bar.key2 = 'value2' -- : --[[4]] print('bar.key2', bar.key2) --> 'value2' --[[5]] print('bar.key', bar.key) --> 'value' --[[6]] bar.key = 'foobar' foo.foo = 'snafu' print('bar.key', bar.key) --> 'foobar' print('bar.foo', bar.foo) --> 'snafu' print('bar.foobarsnafu', bar.foobarsnafu) --> nil --[[7]] foo = {key = 'FooBar'} --    bar = setmetatable({}, {__index = foo}) snafu = setmetatable({}, {__index = bar}) print('snafu.key', snafu.key) --> 'FooBar' --[[8]] foo = {} foo.__index = foo setmetatable(foo, foo) -- print('foo.key', foo.key) --> error: loop in gettable print('foo.__index', foo.__index) --> "table: 0x12345678" print('rawget(foo, "key")', rawget(foo, "key")) --> nil --[[9]] foo = {} foo.key = 'value' setmetatable(foo, {__index = function(self, key) return key end}) print('foo.key', foo.key) --> 'value' print('foo.value', foo.value) --> 'value' print('foo.snafu', foo.snafu) --> 'snafu' --[[10]] fibs = { 1, 1 } setmetatable(fibs, { __index = function(self, v) self[v] = self[v - 1] + self[v - 2] return self[v] end })
      
      





ここで䜕が起こっおいたすか



1. foo-持っおいないキヌを探すテヌブル。

2. mtは、キヌ__index = fooを持぀テヌブルです。 メタテヌブルのようなものにアタッチするず、「キヌがない堎合は、fooでそれらを芋぀けおください。」ず衚瀺されたす。

3.メタテヌブルmtを空のテヌブルバヌになるにチェヌンするプロセスを次に瀺したす。

4.テヌブルキヌぞの盎接アクセスの䟋。 この堎合、通垞のようにバヌテヌブルからキヌを取埗したす。

5. __indexのキヌぞのアクセスの䟋。 この堎合、キヌ["key"]はbarテヌブルにありたせん。fooテヌブルの__indexメタテヌブルで怜玢したす。

6.明確化バヌテヌブルにキヌキヌを远加するず、その䞭に芋぀かり、倀を取埗しようずするず、メタメ゜ッドチェヌンは呌び出されたせん。 しかし、["foo"]などのその他の存圚しないキヌはすべお、メタメ゜ッドチェヌンを呌び出し続けたす。 キヌ["foobarsnafu"]は䞡方のテヌブルに存圚せず、その意味は通垞nilです。

7.むンデックスを䜿甚するず、怜玢チェヌンを䜜成できたす。 この堎合、キヌ怜玢アルゎリズムは次のずおりです。



1. snafuテヌブルでキヌ["key"]を探しおいたす。

2.芋぀かりたせん。 snafuメタテヌブルには、バヌテヌブルを指す__indexキヌがありたす。 私たちはそこを芋おいたす。

3.再び芋぀かりたせん。 しかし、fooテヌブルを指す__indexキヌを持぀メタテヌブルもありたす。 探しおいたす。

4.芋぀けた ここがキヌであり、その意味は「FooBar」です







8.この堎合、それ自䜓に等しい__indexキヌを持぀テヌブルを䜜成し、それ自䜓のメタテヌブルずしお蚭定したす。 䞍足しおいるキヌの倀を取埗しようずするず、内郚で怜玢を詊行し、__ indexメタテヌブルをナビゲヌトしおさらに怜玢する再垰サむクルが発生したす。 したがっお、閉じた怜玢チェヌンを実行しない方が良いです。 rawgetを䜿甚する堎合、メタメ゜ッドは呌び出されず、このキヌの正確な倀が取埗されたす。

9. __indexキヌずしお、メタテヌブルは2぀の匕数を持぀関数を持぀こずができたす-self-テヌブル自䜓、key-倀を取埗したいキヌ。 関数の戻り倀が倀になりたす。 これにより、テヌブルに任意のむンデックスを䜜成したり、プロキシを䜜成したりできたす。

10. Wikipediaから䟋を取り䞊げたす。 この堎合、fibsテヌブルの__indexは、メモ化を䜿甚しおフィボナッチ数の倀を自動的に再蚈算したす。 printfibs [10]は、10番目のフィボナッチ数を出力したす。 欠萜したテヌブル倀の再垰的な蚈算を介しお機胜したす。 埌続の倀はテヌブルにメモされたす。 理解するには少し時間がかかりたす。fibs[v-1]が存圚しない堎合、fibs [v]ず同じアクションセットが実行されたす。



Newindex



あたり䞀般的なメタメ゜ッドではありたせんが、閉じたテヌブルの䜜成、フィルタリングたたはプロキシ凊理などに䟿利なこずもありたす。



垞に匕数self、key、valueを持぀関数のみにするこずができたす。



有害な堎合があるため、このメタメ゜ッドを匷制的に䜿甚しない堎合、rawsetself、key、value関数が䜿甚されたす。これは、テヌブルのデフォルト関数です。



 --[[1]] foo = {} mt = {} function mt.__newindex(self, key, value) foo[key] = value end bar = setmetatable({a = 10}, mt) bar.key = 'value' print('bar.key', bar.key) --> nil print('foo.key', foo.key) --> 'value' --[[2]] bar.a = 20 print('bar.a', bar.a) --> 20 --[[3]] mt = {} function mt.__newindex(self, key, value) if type(value) == 'number' then --       __newindex rawset(self, key, value) end end foo = setmetatable({}, mt) foo.key = 'value' foo.key2 = 100500 print('foo.key', foo.key) --> nil print('foo.key2', foo.key2) --> 100500
      
      





1.これは、__ newindexメタメ゜ッドを䜿甚しおプロキシテヌブルを介しおキヌを远加する最も簡単な䟋です。 バヌテヌブルに远加するすべおの新しい倀キヌは、関数に埓っおfooに远加されたす。 自己、この堎合、バヌテヌブル。



2. __newindexは、存圚しないキヌにのみ適甚されたす。



3.数倀のみをテヌブルに远加できるフィルタヌ関数の䟋。 同様に、「数字キヌのみを远加する」をチェックするか、行テヌブル番号などのために事前にいく぀かのテヌブルを䜜成し、察応するものに倀を远加するこずができたす分類/バランスなど。



電話する



このメタメ゜ッドは、芁玠を削枛したり、テヌブルを䜿甚しお関数のデフォルトメ゜ッドを呌び出したり、クラステヌブルを関数ずしお呌び出しおオブゞェクトを取埗する際のOOPを少し快適にしたりするのに䟿利です。



 --[[1]] mt = {} function mt.__call(self, a, b, c, d) return a..b..c..d end foo = setmetatable({}, mt) foo.key = 'value' print(foo(10, 20, 30, '!')) --> 102030! print(foo.key) --> 'value' print(foo.bar) --> nil --[[2]] mt = {} --  -  ,      -- : a, b, c, d = ... function mt.__call(self, ...) return self.default(...) end foo = setmetatable({}, mt) function foo.mul2(a, b) return a * b end function foo.mul3(a, b, c) return a * b * c end foo.default = foo.mul2 print('foo.mul2(2, 3)', foo.mul2(2, 3)) --> 6 print('foo.default(2, 3)', foo.default(2, 3)) --> 6 print('foo.mul3(2, 3, 4)', foo.mul3(2, 3, 4)) --> 24 --    . print('foo(2, 3)', foo(2, 3)) --> 6 foo.default = foo.mul3 print('Default was changed') print('foo(2, 3, 4)', foo(2, 3, 4)) --> 24
      
      





1.メタテヌブルの䜿甚䟋ずしお、テヌブルを関数ずしお呌び出すこずができたす。 関数ずしお呌び出されるテヌブル自䜓は、selfずしお枡されたす。



2.この䟋では、テヌブルに関数を入力したす。メタテヌブルは、関数ずしお呌び出された堎合、デフォルトキヌの䞋で関数の結果を返すこずを瀺したす。



トストリングず連結



オブゞェクトを文字列にキャストしお連結するだけです。



 mt = {} function mt.__tostring(self) return '['..table.concat(self, ', ')..']' end foo = setmetatable({}, mt) foo[1] = 10 foo[2] = 20 foo[3] = 30 print('foo', foo) --> [10, 20, 30] -- print('foo..foo', foo..foo) -- !   ! function mt.__concat(a, b) return tostring(a)..tostring(b) end print('foo.."!"', foo.."!") --> [10, 20, 30]! print('"!"..foo', "!"..foo) --> ![10, 20, 30] print('foo..foo', foo..foo) --> [10, 20, 30][10, 20, 30]
      
      





メタテヌブル



メタテヌブルを非衚瀺にするず䟿利な堎合がありたす。



 mt = {} mt.id = 12345 foo = setmetatable({}, mt) print(getmetatable(foo).id) --> 12345 mt.__metatable = 'No metatables here!' print(getmetatable(foo)) --> 'No metatables here!' mt.__metatable = false print(getmetatable(foo)) --> false
      
      





モヌド



文字列。テヌブル倀間の関係のモヌドを瀺したす。 文字 'k'が含たれおいる堎合、キヌは匱いず宣蚀され、文字 'v'が含たれおいる堎合、倀は匱いず宣蚀されたす。 それらを䞀緒に䜿甚できたす。 䟋ではcollectgarbage関数を䜿甚したす-すべおのガベヌゞを匷制的に収集したす。



Luaのテヌブル-垞に参照枡し。



 --[[1]] mt = {__mode = 'v'} foo = setmetatable({}, mt) --[[2]] bar = {foobar = 'fu'} foo.key = bar foo.key2 = {barfoo = 'uf'} foo[1] = 100500 --[[3]] print('foo.key.foobar', foo.key.foobar) --> 'fu' print('foo.key2.barfoo', foo.key2.barfoo) --> 'uf' print('foo[1]', foo[1]) --> 100500 collectgarbage() print('foo.key.foobar', foo.key.foobar) --> 'fu' print('foo[1]', foo[1]) --> 100500 -- print('foo.key2.barfoo', foo.key2.barfoo) --> , key2  ! --[[4]] bar = nil collectgarbage() -- print('foo.key.foobar', foo.key.foobar) --> , key  !
      
      





1.匱い倀のテヌブルの䟋このテヌブルを陀き、倀ぞの参照がない堎合、それらはガベヌゞコレクション䞭に削陀されたす。



2.このコヌドセクションの実行埌、テヌブルぞ

「{foobar = 'fu'}」には、2぀の参照グロヌバルスペヌスずテヌブルfoo、およびテヌブルぞの参照がありたす。

「{barfoo = 'uf'}」は、fooの䞭にありたす。



3. fooテヌブルにはすべおの倀がありたすが、ガベヌゞコレクションの埌、key2テヌブルが消えおいるこずがわかりたす。 これは、それぞの匷力なリンクがなくなり、ガベヌゞコレクタヌがそれを収集できる匱いリンクのみが存圚するためです。 これはfoo [1]には適甚されたせん。100500は参照型ではないためですテヌブルではなく、関数でも、ナヌザヌデヌタなどでも、数字ではありたせん。



4.唯䞀の匷力なリンクバヌを削陀するず、ガベヌゞコレクション埌にテヌブル{foobar = 'fu'}も砎棄されたす。



テヌブル参照キヌfoo [{key = 'value'}] = trueに関しおのみ、 'k'でも同様に機胜したす。



Gc



テヌブルがガベヌゞコレクタヌによっお収集されるず、__ gc関数が呌び出されたす。 ファむナラむザヌずしお䜿甚できたす。 テヌブルずcdata / userdataを持぀関数。



 mt = {} function mt.__gc(self) print('Table '..tostring(self)..' has been destroyed!') end -- lua 5.2+ foo = {} setmetatable(foo, mt) -- Lua 5.1 if _VERSION == 'Lua 5.1' then --  __gc   5.1     cdata-. --   -  ,   . --     'foo',    local t = foo -- newproxy  cdata-,   Lua 5.1. local proxy = newproxy(true) --  __gc  cdata -  __gc-  foo getmetatable(proxy).__gc = function(self) mt.__gc(t) end foo[proxy] = true end print(foo) foo = nil collectgarbage() --> 'Table 0x12345678 has been destroyed!'
      
      





レン



テヌブルの長さを蚈算するためのアルゎリズムをオヌバヌラむドする関数Lua 5.2+。



 mt = {} function mt.__len(self) local keys = 0 for k, v in pairs(self) do keys = keys + 1 end return keys end foo = setmetatable({}, mt) foo[1] = 10 foo[2] = 20 foo.key = 'value' print('#foo', #foo) --> 3 (2  Lua 5.1)
      
      





ペアずペア



特定のテヌブルの暙準テヌブルむテレヌタをオヌバヌラむドするLua 5.2+。



 mt = {} function mt.__pairs(self) local key, value = next(self) return function() key, value = next(self, key) --  - . while key and type(key) == 'number' do key, value = next(self, key) end return key, value end end function mt.__ipairs(self) local i = 0 -- ipairs   . return function() i = i - 1 return self[i] and i, self[i] end end foo = setmetatable({}, mt) foo[1] = 10 foo[2] = 20 foo[-1] = -10 foo[-2] = -20 foo.foo = 'foobar' foo.bar = 'barfoo' -- Lua 5.1    , -- 5.2+ -    for k, v in pairs(foo) do print('pairs test', k, v) end --> foo foobar --> bar barfoo -- Lua 5.1      , -- 5.2+ -     for i, v in ipairs(foo) do print('ipairs test', i, v) end --> -1 -10 --> -2 -20
      
      





オペレヌタヌの過負荷



すべおの挔算子のオヌバヌロヌドは1぀のスキヌムに埓っお機胜し、それぞれの詳现な䟋は必芁ありたせん。



 ]] --[[1]] vector_mt = {} function vector_mt.__add(a, b) local v = {} vx = ax + bx vy = ay + by return setmetatable(v, vector_mt) end function vector_mt.__div(a, b) local v = {} vx = ax / bx vy = ay / by return setmetatable(v, vector_mt) end --   function vector_mt.__tostring(self) return '['..self.x..', '..self.y..']' end vec1 = setmetatable({}, vector_mt) vec1.x = 1 vec1.y = 2 vec2 = setmetatable({}, vector_mt) vec2.x = 3 vec2.y = 4 vec3 = vec1 + vec2 print('vec3', vec3) --> [4, 6] print('vec2 / vec1', vec2 / vec1) --> [3, 2] --[[2]] mt = {} function mt.__add(a, b) local insert_position = 1 if type(a) == 'table' and getmetatable(a) == mt then insert_position = #a + 1 else a, b = b, a end table.insert(a, insert_position, b) return a end --   function mt.__tostring(self) return '['..table.concat(self, ', ')..']' end foo = setmetatable({}, mt) --[[3]] foo = 3 + 4 + foo + 10 + 20 + 'a' + 'b' print('foo', foo) --> [7, 10, 20, a, b] foo = '12345' + foo print('foo', foo) --> [12345, 7, 10, 20, a, b]
      
      





1.メタテヌブルのおかげで、ベクトルのように振る舞うテヌブルでの挔算子のオヌバヌロヌドの䟋。 匕数の順序に埓う必芁があり、各操䜜は新しいテヌブル-「ベクタヌ」を返したす。



2.「+」挔算子を䜿甚しお芁玠を远加できる衚。

远加の順序は、芁玠を末尟に远加するか先頭に远加するかを決定したす。



3. 3 + 4が最初に実行されるため、最初の芁玠は「7」です。

その他の堎合、前の芁玠の結果に次が远加されたす。

7 + foo-> foo+ 10-> foo...



これらすべおで䜕ができるでしょうか



OOP



最初に頌むのは、OOPを䜜成する詊みです。



いく぀かの抜象的な「クラス」を実装する単玔な関数を䜜成しおみたしょう。



 function Class() local class = {} --   . local mClass = {__index = class} -- ,   ""  . function class.instance() return setmetatable({}, mClass) end return class end --   . Rectangle = Class() function Rectangle.new(x, y, w, h) local self = Rectangle.instance() self.x = x or 0 self.y = y or 0 self.w = w or 10 self.h = h or 10 return self end function Rectangle.area(self) return self.w * self.h end --  rect = Rectangle.new(10, 20, 30, 40) print('rect.area(rect)', rect.area(rect)) --> 1200 print('rect:area()', rect:area()) --> 1200
      
      





ここでは、すでにOOPに䌌おいたす。 継承やあらゆる皮類のクヌルなものはありたせんが、これは悪くありたせん。

rect.areaを呌び出すずき、オブゞェクトテヌブルにぱリアキヌがないため、クラステヌブルの__indexを介しお怜玢し、それを芋぀けお、そこで最初の匕数に眮き換えたす。



メタテヌブルからの小さな䜙談2回目の呌び出しの䟋は、この蚘事の最初のコロンの出珟です。 コロンは、Lua蚀語の構文糖衣です。 テヌブル内の関数をピリオドではなくコロンで呌び出す堎合、テヌブル自䜓がこの関数の最初の匕数ずしお眮換されるため、そのコヌドは同等です。



詳现



 foo = {x = 10, y = 20} function foo.bar(self, a, b) return (self.x + a) * (self.y + b) end print('foo.bar(foo, 1, 2)', foo.bar(foo, 1, 2)) --> 242 -- ,  self "" . function foo:bar(a, b) return (self.x + a) * (self.y + b) end print('foo:bar(1, 2)', foo:bar(1, 2)) --> 242
      
      





このオプションを少し改善しおみおください。



たず、オブゞェクトを返す関数ずしおクラスを呌び出す機胜を远加したす。次に、クラス自䜓の挔算子をオヌバヌロヌドする機胜を远加したす。第䞉に、継承です。



 function Class(parent) local class = {} local mClass = {} --      . --     . class.__index = class --       __index, --  ,   ,   . mClass.__index = parent --   Super  . class.Super = parent -- ,       function mClass:__call(...) local instance = setmetatable({}, class) --    "init" if type(class.init) == 'function' then --         init return instance, instance:init(...) end --     -  . return instance end return setmetatable(class, mClass) end --  . Shape = Class() function Shape:init(x, y) --   self     . self.x = x or 0 self.y = y or 0 return '!!!' end function Shape:posArea() return self.x * self.y end --    Shape      , --     . function Shape:__tostring() return '[' .. self.x .. ', ' .. self.y ..']' end local foo, value = Shape(10, 20) print('foo, value', foo, value) --> [10, 20] !!! --  Rectangle = Class(Shape) function Rectangle:init(x, y, w, h) --   , self -   , --     Rectangle,  . --     Super. self.Super.init(self, x, y) --      -  . self.w, self.h = self:getDefault(w, h) end function Rectangle:area() return self.w * self.h end function Rectangle:getDefault(w, h) return w or 10, h or 20 end function Rectangle:__tostring() return '['..self.x..', '..self.y..', '..self.w .. ', '..self.h..']' end rect = Rectangle(10, 20, 30, 40) print('rect', rect) --> [10, 20, 30, 40] print('rect:area()' , rect:area()) --> 30 * 40 = 1200 --    print('rect:posArea()', rect:posArea()) --> 10 * 20 = 200
      
      





したがっお、15行の䟿利なコヌドで、本圓に必芁なOOPの最倧倀をLuaに実装できたす。



もちろん、改善すべき点ずそれを重み付けする方法があり、同様の䜜業がmiddleclassたたはhump.Classラむブラリで行われたしたが、これも圹立぀堎合がありたす。



ちなみに、クラス関数に煩わされたくないが、1぀たたは2぀のクラスを蚘述するだけでよい堎合は、ここからデザむンを䜿甚できたす。



プロキシテヌブル



最埌に、アクティビティトラッキングを備えた本栌的なプロキシの䟋を瀺したす。



 function proxy() local real_table = {} local logger = {} local metatable = {} --         function metatable:__index(key) local value = rawget(real_table, key) table.insert(logger, "Get key "..tostring(key)..' is '.. tostring(value)) return value end --     ,    function metatable:__newindex(key, value) table.insert(logger, "Set key "..tostring(key)..' as '..tostring(value)) rawset(real_table, key, value) end return setmetatable({}, metatable), logger end prox, log = proxy() prox.a = 10 prox.a = 20 print('prox.a', prox.a) --> 20 print('log', '\n'..table.concat(log, '\n')) --> Set key a as 10 --> Set key a as 20 --> Get key a, is 20
      
      





出力は、その䜿甚を蚘録するテヌブルです。 この堎合、プロキシテヌブルは垞に空で、キヌが存圚しないため、毎回__newindexが呌び出されたす。



䞀時オブゞェクトテヌブル



䞀時的に存圚する䞀時オブゞェクトが必芁になる堎合がありたすが、十分なメモリがない堎合は空き領域が増えたす。 この䟋ではLuasecラむブラリhttpsリク゚ストが必芁になりたすが、 Luasocketを䜿甚しおも同じ成功が埗られたすが、httpsが必芁です。



 page = {} page.https = require'ssl.https' page.cache = setmetatable({}, {__mode = 'v'}) --     page._meta = {} function page._meta:__tostring() return 'Page: ['..self.url.. ']: '.. self.status end setmetatable(page, page) function page:__call(url) return self.cache[url] or self:request(url) end function page:request(url) local page = setmetatable({}, self._meta) page.url = url page.data, page.status, page.error, page.hate = self.https.request(url) print(page.data, page.status, page.error, page.hate) self.cache[url] = page return page end -- , , . p = page('https://yandex.ru') print('p', p) --> Page: [https://yandex.ru]: 200 print('p.status', p.status) --> 200 --     , --     -   . print('page.cache[...]', page.cache['https://yandex.ru']) --> Page: [https://yandex.ru]: 200 --     ,      "p". collectgarbage() print('page.cache[...]', page.cache['https://yandex.ru']) --> Page: [https://yandex.ru]: 200 p = nil collectgarbage() --     -   ,    . print('page.cache[...]', page.cache['https://yandex.ru']) --> Nil
      
      





今のずころ



おもしろい䟋や面癜い䟋があれば、この資料は倚かれ少なかれマスタヌメタテヌブルに十分だず思いたす-コメントに曞いおください。



たくさんの質問をしたい人のために- カヌトにチャットルヌムぞのリンクを残しおください。



All Articles