__len 其实很鸡肋

5.1 的 table 是不支持 __len 的

Posted by ms2008 on October 13, 2016

我们知道在 Lua 中取 table 的长度,可以使用 # 。但是这里有个前提,就是 table 必须是一个序列。如果一个 table 有「空洞」,那么用 # 取到的结果将没有明确含义!!!

但是根据 Lua 官方文档的说法,这个行为是可以通过 __len 元方法来改变的:

A program can modify the behavior of the length operator for any value but strings through the __len metamethod

需要注意的是这个行为并不适用于 Lua 5.1,__len 对于 table 是从 5.2 以后才开始支持的。我们来验证下:

local t = {10, nil, 30, nil}
print(#t)
local mt = {
    __len = function ()
        return 4
    end
}
setmetatable(t, mt)
print("==> ", getmetatable(t).__len())
print(#t)

-- Lua-5.1.4
-- output:
-- 1
-- ==> 	4
-- 1

-- Lua-5.2.4
-- output:
-- 1
-- ==> 	4
-- 4

也许有的同学还有疑问:那我们可不可以重载对于 string# 运算?

local str = "test str"
local mt = getmetatable(str)
mt.__len = function ()
    return 5
end
debug.setmetatable(str, mt)
print(#str)

-- output:
-- 8

答案显然是不可以的,Lua wiki 上也有标明:

All types except string now respect __len metamethod.

最后,Lua 的很多标准库均依赖 __len 的正确定义,如:table.inserttable.remove 等等。如果需要重载 __len 时务必要格外小心,否则可能既没有获得什么好处,还会使 Lua 标准库无法正常工作。