Lua中设置table为只读属性的方法详解

时间:2022-05-03 15:54:48

项目中部分只读表易被人误改写,故决定在非线上环境里对这些表附加只读属性,方便在出现误改写的时候抛出lua错误,最终版代码如下:

  1. --[[------------------------------------------------------------------------------ 
  2. -** 设置table只读 出现改写会抛出lua error 
  3. -- 用法 local cfg_proxy = read_only(cfg) retur cfg_proxy 
  4. -- 增加了防重置设置read_only的机制 
  5. -- lua5.3支持 1)table库支持调用元方法,所以table.remove table.insert 也会抛出错误, 
  6. --  2)不用定义__ipairs 5.3 ipairs迭代器支持访问元方法__index,pairs迭代器next不支持故需要元方法__pairs 
  7. -- 低版本lua此函数不能完全按照预期工作 
  8. *]] 
  9. function read_only(inputTable) 
  10.  local travelled_tables = {} 
  11.  local function __read_only(tbl) 
  12.  if not travelled_tables[tbl] then 
  13.   local tbl_mt = getmetatable(tbl) 
  14.   if not tbl_mt then 
  15.   tbl_mt = {} 
  16.   setmetatable(tbl, tbl_mt) 
  17.   end 
  18.  
  19.   local proxy = tbl_mt.__read_only_proxy 
  20.   if not proxy then 
  21.   proxy = {} 
  22.   tbl_mt.__read_only_proxy = proxy 
  23.   local proxy_mt = { 
  24.    __index = tbl, 
  25.    __newindex = function (t, k, v) error("error write to a read-only table with key = " .. tostring(k)) end, 
  26.    __pairs = function (t) return pairs(tbl) end, 
  27.    -- __ipairs = function (t) return ipairs(tbl) end, 5.3版本不需要此方法 
  28.    __len = function (t) return #tbl end, 
  29.    __read_only_proxy = proxy 
  30.   } 
  31.   setmetatable(proxy, proxy_mt) 
  32.   end 
  33.   travelled_tables[tbl] = proxy 
  34.   for k, v in pairs(tbl) do 
  35.   if type(v) == "table" then 
  36.    tbl[k] = __read_only(v) 
  37.   end 
  38.   end 
  39.  end 
  40.  return travelled_tables[tbl] 
  41.  end 
  42.  return __read_only(inputTable) 
  43. end 

测试代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
local t0 = {k = 1}
local t2 = {
 fdsf = {456}
}
local t1 = {
  a = {456, 89},
  b = {456,ddss = 9, t2 = t2},
  d = 45,
  e = "string",
}
t1.c=t1
 
local t3 = read_only(t1)
 
print(t3.d, t3.c.e, t3.c.c.b.t2.fdsf)
function q1() t3.d = 4555 end
function q2() t3.c.d = 90 end
function q3() t3.c.c.b.t2.fdsf =90 end
function q4() table.remove(t3.a) end
function q5() t3.b[ddss] = nil end
function q6() t3[f] = 89 end
function q7() table.insert(t3.a, 999) end
 
print(pcall(q1))
print(pcall(q2))
print(pcall(q3))
print(pcall(q4))
print(pcall(q5))
print(pcall(q6))
print(pcall(q7))
print(t3.a[1])
for k,v in pairs(t3) do
 print("===pairs t3:",k,v)
end
for k,v in pairs(t3.a) do
 print("===pairs t3.a:",k,v)
end
for k,v in ipairs(t3) do
 print("===ipairs t3:",k,v)
end
for k,v in ipairs(t3.a) do
 print("===ipair t3.a",k,v)
end
print("len t3:",#t3)
print("len t3.a:", #t3.a)
 
local t4 = read_only(t2)
 
local t5 = read_only(t0)
local t6 = read_only(t0)
 
print(t3.b.t2, read_only(t2))
print(t5, t6, t0)

测试环境https://www.lua.org/cgi-bin/demo  lua5.3.4:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
string table: 0x20d4ba0
false input:17: error write to a read-only table with key = d
false input:17: error write to a read-only table with key = d
false input:17: error write to a read-only table with key = fdsf
false input:17: error write to a read-only table with key = 2
false input:17: error write to a read-only table with key = nil
false input:17: error write to a read-only table with key = nil
false input:17: error write to a read-only table with key = 3
===pairs t3: e string
===pairs t3: b table: 0x20ccd60
===pairs t3: a table: 0x20d4e70
===pairs t3: d 45
===pairs t3: c table: 0x20ca700
===pairs t3.a: 1 456
===pairs t3.a: 2 89
===ipair t3.a 1 456
===ipair t3.a 2 89
len t3: 0
len t3.a: 2
table: 0x20d4870 table: 0x20d4870
table: 0x20d5690 table: 0x20d5690 table: 0x20d1140

代码思路设计:

1.使用proxy={}空表而不是目标表tbl来设置__newindex是因为__newindex必须在原表里面不存在才会调用,这样就依然可以对已存在的字段进行改写

?
1
2
3
4
5
__newindex: The indexing assignment table[key] = value. Like the index event, this event happens when table is not a table or when key is not present in table. The metamethod is looked up in table.
 
Like with indexing, the metamethod for this event can be either a function or a table. If it is a function, it is called with table, key, and value as arguments. If it is a table, Lua does an indexing assignment to this table with the same key and value. (This assignment is regular, not raw, and therefore can trigger another metamethod.)
 
Whenever there is a __newindex metamethod, Lua does not perform the primitive assignment. (If necessary, the metamethod itself can call rawset to do the assignment.)

2.避免出现table的互相引用,加入travelled_tables存储已经设置过proxy的table的映射

3.对于原表tbl的访问使用__index=tbl

4.对于表查长度使用__len= function () return #tbl end

5.对于遍历pairs,查到lua5.3的pairs默认迭代器next不支持访问元表__index,故直接__pairs = function () return pairs(tbl) end,以此来生成对目标表的迭代遍历

6.对于ipairs,查到lua5.3 ipairs函数生成的迭代器默认就支持访问元表__index,故不需要添加__ipairs

     8.2 – Changes in the Libraries

     •The ipairs iterator now respects metamethods and its __ipairs metamethod has been deprecated.

7.对于table.insert , table.remove不用特殊处理,lua5.3的table lib支持元表操作,故依然会抛错

      8.2 – Changes in the Libraries

      •The Table library now respects metamethods for setting and getting elements.

8.避免重复创建read_only,每个tbl只创建一个proxy代理,在tbl的metatable里和proxy的metatable里都设置属性__read_only_proxy,可以直接访问获得

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:http://www.cnblogs.com/vanishfan/p/6909153.html