Lua中设置table为只读属性的方法详解
项目中部分只读表易被人误改写,故决定在非线上环境里对这些表附加只读属性,方便在出现误改写的时候抛出lua错误,最终版代码如下:
--[[------------------------------------------------------------------------------
-**设置table只读出现改写会抛出luaerror
--用法localcfg_proxy=read_only(cfg)returcfg_proxy
--增加了防重置设置read_only的机制
--lua5.3支持1)table库支持调用元方法,所以table.removetable.insert也会抛出错误,
--2)不用定义__ipairs5.3ipairs迭代器支持访问元方法__index,pairs迭代器next不支持故需要元方法__pairs
--低版本lua此函数不能完全按照预期工作
*]]
functionread_only(inputTable)
localtravelled_tables={}
localfunction__read_only(tbl)
ifnottravelled_tables[tbl]then
localtbl_mt=getmetatable(tbl)
ifnottbl_mtthen
tbl_mt={}
setmetatable(tbl,tbl_mt)
end
localproxy=tbl_mt.__read_only_proxy
ifnotproxythen
proxy={}
tbl_mt.__read_only_proxy=proxy
localproxy_mt={
__index=tbl,
__newindex=function(t,k,v)error("errorwritetoaread-onlytablewithkey="..tostring(k))end,
__pairs=function(t)returnpairs(tbl)end,
--__ipairs=function(t)returnipairs(tbl)end,5.3版本不需要此方法
__len=function(t)return#tblend,
__read_only_proxy=proxy
}
setmetatable(proxy,proxy_mt)
end
travelled_tables[tbl]=proxy
fork,vinpairs(tbl)do
iftype(v)=="table"then
tbl[k]=__read_only(v)
end
end
end
returntravelled_tables[tbl]
end
return__read_only(inputTable)
end
测试代码如下:
localt0={k=1}
localt2={
fdsf={456}
}
localt1={
a={456,89},
b={456,ddss=9,t2=t2},
d=45,
e="string",
}
t1.c=t1
localt3=read_only(t1)
print(t3.d,t3.c.e,t3.c.c.b.t2.fdsf)
functionq1()t3.d=4555end
functionq2()t3.c.d=90end
functionq3()t3.c.c.b.t2.fdsf=90end
functionq4()table.remove(t3.a)end
functionq5()t3.b[ddss]=nilend
functionq6()t3[f]=89end
functionq7()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])
fork,vinpairs(t3)do
print("===pairst3:",k,v)
end
fork,vinpairs(t3.a)do
print("===pairst3.a:",k,v)
end
fork,vinipairs(t3)do
print("===ipairst3:",k,v)
end
fork,vinipairs(t3.a)do
print("===ipairt3.a",k,v)
end
print("lent3:",#t3)
print("lent3.a:",#t3.a)
localt4=read_only(t2)
localt5=read_only(t0)
localt6=read_only(t0)
print(t3.b.t2,read_only(t2))
print(t5,t6,t0)
测试环境https://www.lua.org/cgi-bin/demo lua5.3.4:
stringtable:0x20d4ba0 falseinput:17:errorwritetoaread-onlytablewithkey=d falseinput:17:errorwritetoaread-onlytablewithkey=d falseinput:17:errorwritetoaread-onlytablewithkey=fdsf falseinput:17:errorwritetoaread-onlytablewithkey=2 falseinput:17:errorwritetoaread-onlytablewithkey=nil falseinput:17:errorwritetoaread-onlytablewithkey=nil falseinput:17:errorwritetoaread-onlytablewithkey=3 ===pairst3:estring ===pairst3:btable:0x20ccd60 ===pairst3:atable:0x20d4e70 ===pairst3:d45 ===pairst3:ctable:0x20ca700 ===pairst3.a:1456 ===pairst3.a:289 ===ipairt3.a1456 ===ipairt3.a289 lent3:0 lent3.a:2 table:0x20d4870table:0x20d4870 table:0x20d5690table:0x20d5690table:0x20d1140
代码思路设计:
1.使用proxy={}空表而不是目标表tbl来设置__newindex是因为__newindex必须在原表里面不存在才会调用,这样就依然可以对已存在的字段进行改写
__newindex:Theindexingassignmenttable[key]=value.Liketheindexevent,thiseventhappenswhentableisnotatableorwhenkeyisnotpresentintable.Themetamethodislookedupintable. Likewithindexing,themetamethodforthiseventcanbeeitherafunctionoratable.Ifitisafunction,itiscalledwithtable,key,andvalueasarguments.Ifitisatable,Luadoesanindexingassignmenttothistablewiththesamekeyandvalue.(Thisassignmentisregular,notraw,andthereforecantriggeranothermetamethod.) Wheneverthereisa__newindexmetamethod,Luadoesnotperformtheprimitiveassignment.(Ifnecessary,themetamethoditselfcancallrawsettodotheassignment.)
2.避免出现table的互相引用,加入travelled_tables存储已经设置过proxy的table的映射
3.对于原表tbl的访问使用__index=tbl
4.对于表查长度使用__len=function()return#tblend
5.对于遍历pairs,查到lua5.3的pairs默认迭代器next不支持访问元表__index,故直接__pairs=function()returnpairs(tbl)end,以此来生成对目标表的迭代遍历
6.对于ipairs,查到lua5.3ipairs函数生成的迭代器默认就支持访问元表__index,故不需要添加__ipairs
8.2–ChangesintheLibraries
•Theipairsiteratornowrespectsmetamethodsandits__ipairsmetamethodhasbeendeprecated.
7.对于table.insert,table.remove不用特殊处理,lua5.3的tablelib支持元表操作,故依然会抛错
8.2–ChangesintheLibraries
•TheTablelibrarynowrespectsmetamethodsforsettingandgettingelements.
8.避免重复创建read_only,每个tbl只创建一个proxy代理,在tbl的metatable里和proxy的metatable里都设置属性__read_only_proxy,可以直接访问获得
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。