lua元表和元方法 《lua程序设计》 13章 读书笔记
lua中每个值都有一个元表,talble和userdata可以有各自独立的元表,而其它类型的值则共享其类型所属的单一元表。lua在创建table时不会创建元表。
t = {}
print(getmetatable(t)) --显示过元表 此时是nil
--可以用setmetatable来设置或修改任何table的元表 t1 = {} setmetatable(t,t1) assert(getmetatable(t) == t1)
任何table可以作为任何值的元表,而一组相关的table可以共享一个通用的元表,此元表描述了一个共同的行为。一个tabel甚至可以作为它自己的元表,用于描述其特有行为。
在lua中,只能设置table的元表。要设置其它类型的元表,必须通过C代码来完成
print(getmetatable("hi")) --005DECD8 说明字符串有元表 print(getmetatable(10)) --number没有元表
13.1 算术类的元方法
Set = {} --集合
local mt = {} --集合元表
--根据参数列表中的值创建一个新的集合 function Set.new(l) local set = {} setmetatable(set,mt) --指定 table set的元表为mt for k,v in ipairs(l) do set[v] = true --注意,是拿索来当数据用的 end return set end function Set.union(a,b) local res = Set.new{} for k,v in pairs(a) do res[k] = true end for k,v in pairs(b) do res[k] = true end return res end function Set.intersection(a,b) local res = Set.new{} for k,v in pairs(a) do if b[k] then res[k] = true end end return res end function Set.tostring(set) local l = {} for k,v in pairs(set) do l[#l + 1] = k end return "{" .. table.concat(l,", ") .. "}" end function Set.print(s) print(Set.tostring(s)) end --将元方法加入元表 mt.__add = Set.union --指定加号为求并集的方法 mt.__mul = Set.intersection --指定乘号为交集的方法 s1 = Set.new{11,22,31,44,56} s2 = Set.new{66,33,22,31} s3 = s1 + s2 --求并集 Set.print(s3) --输出 {11, 31, 66, 22, 33, 56, 44} s4 = s1 * s2 --求交集 Set.print(s4) --输出 {31, 22}
13.2 关系类元方法
关系是指 __eq(等于)、__lt(小于)等
mt.__le = function(a,b)
for k in pairs(a) do if not b[k] then return false end end return true end mt.__lt = function(a,b) return a<=b and not (b<=a) end mt.__eq = function(a,b) return a<=b and b<=a end ss1 = Set.new{2,4} ss2 = Set.new{4,10,2} print(ss1<=ss2) --true print(ss1<ss2) --true print(ss1>=ss1) --true print(ss1>ss1) --false print(ss1 == ss2*ss1) --true
13.3 库定义的元方法
tostring是一个典型的实例。它能将各种类型的值表示为简单的文本格式
print({}) ----table: 003ECEF0
函数总是调用tostring来格式化输出。当格式化任意值时,tostring会检测该值是否有一个 __tostring元方法。如果有,他就调用这个方法用来作为tostring的返回值
在集合实例中,我们定议了将任命表示为字符串的方法,我们可以设置元表的__tostring字段
mt.__tostring = Set.tostring
sstext = Set.new{33,55,6666}
print(sstext) --{55, 33, 6666}
假设想要保护集合的元表,使用户即不能看也不能修改集合的元表。那么就需要用到__metatable。当设置了该字段时,getmetatable就会返回这个字段的值,而setmetatable会引发一个错误
mt.__metatable = "not your business"
sstext1 = Set.new{}
print(getmetatable(sstext1)) --not your business setmetatable(s1,{})
13.4 table 访问的元方法
13.4.1 __index元方法
当访问一个table中不存在的字段中时,如果这个字段不存在得到nil,但是如果这个table有一个元方法__index那么如果没有这个字段,就由这个元方法来提供结果
Window = {}
Window.prototype = {x=0,y=0,width = 100,height = 100} Window.mt = {} function Window.new(o) setmetatable(o,Window.mt) return o end --现在定义一个元方法 Window.mt.__index = function(table,key) return Window.prototype[key] end w = Window.new{x=10,y=20} print(w.width) -- 100 window实际上没有width这个字段
__index元方法还可以是一个table
13.4.2 __newindex元方法
与__index不同的是__index是在查询的时候用的而_newindes是在更新的时候用的
13.4.3具有默认值的table
以下代码为table设置默认值
function setDefault(t,d)
local mt = {__index = function() return d end} setmetatable(t,mt) end
13.4.4 跟踪table的访问
__index和__newindex都是在table中没有所需的index才发挥作用。因为只有table保持空才能捕捉到所有对他的访问,为了监视一个table的所有访问就得为真正的 table 创建一个代理
t_src = {} --要跟踪的表
local _t = t_src
t = {} --创建代理 --创建元表 local mt = { __index = function(t,k) print("*access to element " .. tostring(k)) return _t[k] end, __newindex = function(t,k,v) print("*update of element " .. tostring(k) .. " to " .. tostring(v)) _t[k] = v end } setmetatable(t,mt) t[2] = "hello" -- *update of element 2 to hello print(t[2]) --*access to element 2
13.4.5 只读的table
只读table与上一节跟踪table类似,是通过__newindex来限制修改table内存
出处:
rawget是为了绕过__index而出现的,直接点,就是让__index方法的重写无效。(我这里用到"重写"二字,可能不太对,希望能得到纠正)
Window = {}
Window.prototype = {x = 0 ,y = 0 ,width = 100 ,height = 100,}
Window.mt = {}
function Window.new(o)
setmetatable(o ,Window.mt)
return o
end
Window.mt.__index = function (t ,key)
return 1000
end
Window.mt.__newindex = function (table ,key ,value)
if key == "wangbin" then
rawset(table ,"wangbin" ,"yes,i am")
end
end
w = Window.new{x = 10 ,y = 20}
print(rawget(w ,w.wangbin))
打印结果是:nil。这里的元表中__index函数就不再起作用了。
但是rawset呢,起什么作用呢?我们再来运行一段代码。
Window = {}
Window.prototype = {x = 0 ,y = 0 ,width = 100 ,height = 100,}
Window.mt = {}
function Window.new(o)
setmetatable(o ,Window.mt)
return o
end
Window.mt.__index = function (t ,key)
return 1000
end
Window.mt.__newindex = function (table ,key ,value)
table.key = "yes,i am"
end
w = Window.new{x = 10 ,y = 20}
w.wangbin = "55"
然后我们的程序就stack overflow了。可见,程序陷入了死循环。因为w.wangbin这个元素本来就不存在表中,然后这里不断执行进入__newindex,陷入了死循环。
This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The third edition targets Lua 5.2 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.
| Part II. Tables and Objects Chapter 13. Metatables and Metamethods | |
13.1 – Arithmetic Metamethods
In this section, we will introduce a simple example to explain how to use metatables. Suppose we are using tables to represent sets, with functions to compute the union of two sets, intersection, and the like. As we did with lists, we store these functions inside a table and we define a constructor to create new sets:
Set = {} function Set.new (t) local set = {} for _, l in ipairs(t) do set[l] = true end return set end function Set.union (a,b) local res = Set.new{} for k in pairs(a) do res[k] = true end for k in pairs(b) do res[k] = true end return res end function Set.intersection (a,b) local res = Set.new{} for k in pairs(a) do res[k] = b[k] end return res end
To help checking our examples, we also define a function to print sets:
function Set.tostring (set) local s = "{" local sep = "" for e in pairs(set) do s = s .. sep .. e sep = ", " end return s .. "}" end function Set.print (s) print(Set.tostring(s)) end
Now, we want to make the addition operator (`+
´) compute the union of two sets. For that, we will arrange that all tables representing sets share a metatable and this metatable will define how they react to the addition operator. Our first step is to create a regular table that we will use as the metatable for sets. To avoid polluting our namespace, we will store it in the Set
table:
Set.mt = {} -- metatable for sets
The next step is to modify the Set.new
function, which creates sets. The new version has only one extra line, which sets mt
as the metatable for the tables that it creates:
function Set.new (t) -- 2nd version local set = {} setmetatable(set, Set.mt) for _, l in ipairs(t) do set[l] = true end return set end
After that, every set we create with Set.new
will have that same table as its metatable:
s1 = Set.new{10, 20, 30, 50} s2 = Set.new{30, 1} print(getmetatable(s1)) --> table: 00672B60 print(getmetatable(s2)) --> table: 00672B60
Finally, we add to the metatable the so-called metamethod, a field __add
that describes how to perform the union:
Set.mt.__add = Set.union
Whenever Lua tries to add two sets, it will call this function, with the two operands as arguments.
With the metamethod in place, we can use the addition operator to do set unions:
s3 = s1 + s2 Set.print(s3) --> {1, 10, 20, 30, 50}
Similarly, we may use the multiplication operator to perform set intersection:
Set.mt.__mul = Set.intersection Set.print((s1 + s2)*s1) --> {10, 20, 30, 50}
For each arithmetic operator there is a corresponding field name in a metatable. Besides __add
and __mul
, there are __sub
(for subtraction), __div
(for division), __unm
(for negation), and __pow
(for exponentiation). We may define also the field __concat
, to define a behavior for the concatenation operator.
When we add two sets, there is no question about what metatable to use. However, we may write an expression that mixes two values with different metatables, for instance like this:
s = Set.new{1,2,3} s = s + 8
To choose a metamethod, Lua does the following: (1) If the first value has a metatable with an __add
field, Lua uses this value as the metamethod, independently of the second value; (2) otherwise, if the second value has a metatable with an __add
field, Lua uses this value as the metamethod; (3) otherwise, Lua raises an error. Therefore, the last example will call Set.union
, as will the expressions 10 + s
and "hy" + s
.
Lua does not care about those mixed types, but our implementation does. If we run the s = s + 8
example, the error we get will be inside Set.union
:
bad argument #1 to `pairs' (table expected, got number)
If we want more lucid error messages, we must check the type of the operands explicitly before attempting to perform the operation:
function Set.union (a,b) if getmetatable(a) ~= Set.mt or getmetatable(b) ~= Set.mt then error("attempt to `add' a set with a non-set value", 2) end ... -- same as before
Metatable Events |
wiki |
|
A listing of all the 'special' keys in a metatable, and the metamethods which they perform.
- __index - Control 'prototype' inheritance. When accessing "myTable[key]" and the key does not appear in the table, but the metatable has an __index property:
- if the value is a function, the function is called, passing in the table and the key; the return value of that function is returned as the result.
- (and if it doesn't exist in that table, but that table's metatable has an __index property, then it continues on up)
- Use "rawget(myTable,key)" to skip this metamethod.
- __newindex - Control property assignment. When calling "myTable[key] = value", if the metatable has a __newindex key pointing to a function, call that function, passing it the table, key, and value.
- Use "rawset(myTable,key,value)" to skip this metamethod.
- (If the __newindex function does not set the key on the table (using rawset) then the key/value pair is not added to myTable.)
- __mode - Control weak references. A string value with one or both of the characters 'k' and 'v' which specifies that the the keys and/or values in the table are weak references.
- __call - Treat a table like a function. When a table is followed by parenthesis such as "myTable( 'foo' )" and the metatable has a __call key pointing to a function, that function is invoked (passing the table as the first argument, followed by any specified arguments) and the return value is returned.
- __metatable - Hide the metatable. When "getmetatable( myTable )" is called, if the metatable for myTable has a __metatable key, the value of that key is returned instead of the actual metatable.
- __tostring - Control string representation. When the builtin "tostring( myTable )" function is called, if the metatable for myTable has a __tostring property set to a function, that function is invoked (passing myTable to it) and the return value is used as the string representation.
- __len - Control table length. When the table length is requested using the length operator ( '#' ), if the metatable for myTable has a __len key pointing to a function, that function is invoked (passing myTable to it) and the return value used as the value of "#myTable".
- __gc - Userdata finalizer code. When userdata is set to be garbage collected, if the metatable has a __gc field pointing to a function, that function is first invoked, passing the userdata to it. The __gc metamethod is not called for tables. (See http://lua-users.org/lists/lua-l/2006-11/msg00508.html)
Mathematic Operators
- __unm - Unary minus. When writing "-myTable", if the metatable has a __unm key pointing to a function, that function is invoked (passing the table), and the return value used as the value of "-myTable".
- __add - Addition. When writing "myTable + object" or "object + myTable", if myTable's metatable has an __add key pointing to a function, that function is invoked (passing the left and right operators in order) and the return value used.
- ''If both operands are tables, the left table is checked before the right table for the presence of an __add metaevent.
- __sub - Subtraction. Similar to addition, using the '-' operator.
- __mul - Multiplication. Similar to addition, using the '*' operator.
- __div - Division. Similar to addition, using the '/' operator.
- __mod - Modulo. Similar to addition, using the '%' operator.
- __pow - Involution. Similar to addition, using the '^' operator.
- __concat - Concatenation. Similar to addition, using the '..' operator.
Equivalence Comparison Operators
- __eq - Check for equality. This method is invoked when "myTable1 == myTable2" is evaluated, but only if both tables have the exact same metamethod for __eq.
- For example, see the following code:
t1a = {} t1b = {} t2 = {} mt1 = { __eq = function( o1, o2 ) return 'whee' end } mt2 = { __eq = function( o1, o2 ) return 'whee' end } setmetatable( t1a, mt1 ) setmetatable( t1b, mt1 ) setmetatable( t2, mt2 ) print( t1a == t1b ) --> true print( t1a == t2 ) --> false
- If the function returns nil or false, the result of the comparison is false; otherwise, the result is true.
- If
t1
andt2
are referencing the same table, the__eq
method is not invoked fort1 == t2
:
function foo (o1, o2)
print( '__eq call' )
return false
end t1 = {} setmetatable( t1, {__eq = foo} ) t2 = t1 print( t1 == t2 ) --> true -- string '__eq call' not printed (and comparison result is true, not like the return value of foo(...)), so no foo(...) call here t3 = {} setmetatable( t3, {__eq = foo} ) if t1 == t3 then end --> __eq call -- foo(...) was called
- __lt - Check for less-than. Similar to equality, using the '<' operator.
- Greater-than is evaluated by reversing the order of the operands passed to the __lt function.
a > b == b < a
- __le - Check for less-than-or-equal. Similar to equality, using the '<=' operator.
- Greater-than-or-equal is evaluated by reversing the order of the operands passed to the __le function.
a >= b == b <= a