Lua 5.1 中的 __call 元方法实际上是如何工作的?
我正在尝试在 Lua 中创建一个集合实现作为练习。具体来说,我想要采取 Pil2 11.5 中简单的集合实现,并扩展它以包括插入值、删除值等功能。
现在要做的显而易见的方法(以及有效的方法)是这样的:
Set = {}
function Set.new(l)
local s = {}
for _, v in ipairs(l) do
s[v] = true
end
return s
end
function Set.insert(s, v)
s[v] = true
end
ts = Set.new {1,2,3,4,5}
Set.insert(ts, 5)
Set.insert(ts, 6)
for k in pairs(ts) do
print(k)
end
如预期的那样,我得到数字 1 到 6 的输出。但是那些对 Set.insert(s, value)
的调用风格真的相当丑陋。我更愿意能够调用类似于 ts:insert(value)
这样的东西。
我第一次尝试解决这个问题的解决方案看起来像这样:
Set = {}
function Set.new(l)
local s = {
insert = function(t, v)
t[v] = true
end
}
for _, v in ipairs(l) do
s[v] = true
end
return s
end
ts = Set.new {1,2,3,4,5}
ts:insert(5)
ts:insert(6)
for k in pairs(ts) do
print(k)
end
这基本上很好,直到你看到它的输出:
1
2
3
4
5
6
insert
很明显,集合表的成员 insert
函数正在被显示。这甚至比原来的 Set.insert(s, v)
问题更丑陋,而且也很容易出现一些严重的问题(例如,如果 "insert" 是某人试图输入的有效键,会发生什么问题?)。是时候重新看一下书了。如果我尝试这个怎么样呢?:
Set = {}
function Set.new(l)
local s = {}
setmetatable(s, {__call = Set.call})
for _, v in ipairs(l) do
s[v] = true
end
return s
end
function Set.call(f)
return Set[f]
end
function Set.insert(t, v)
t[v] = true
end
ts = Set.new {1,2,3,4,5}
ts:insert(5)
ts:insert(6)
for k in pairs(ts) do
print(k)
end
现在我正在阅读这段代码:
- 当我调用
ts:insert(5)
时,insert
不存在可以调用的事实意味着ts
元表会被搜索 "__call"。 ts
元表的 "__call" 键返回Set.call
。- 现在调用
Set.call
并使用名称insert
,这会导致返回Set.insert
函数。 - 调用
Set.insert(ts, 5)
。
实际发生的事情是这样的:
lua: xasm.lua:26: attempt to call method 'insert' (a nil value)
stack traceback:
xasm.lua:26: in main chunk
[C]: ?
此时,我感到困惑。我完全不知道接下来该做什么。我随机尝试了一个小时以上,试图在这段代码上进行细微调整,但最终的结果是没有可以正常工作的代码。我在这一点上肯定忽略了显而易见的事情是什么?
原文链接 https://stackoverflow.com/questions/6048118
Set = {}
Set.__index = Set
-- 新建一个 Set 对象
function Set:new(collection)
local o = {}
for _, v in ipairs(collection) do
o[v] = true
end
setmetatable(o, self)
return o
end
-- 向 Set 中插入元素
function Set:insert(v)
self[v] = true
end
-- 创建一个 Set 对象
set = Set:new({1,2,3,4,5})
print(set[1]) --> true
print(set[10]) --> nil
set:insert(10)
print(set[10]) --> true
我修改了你的第一个版本,这个版本将提供我认为你正在寻找的功能。
你说:
现在我读这段代码的方式是:
- 当我调用 ts:insert(5) 时,由于 insert 并不存在于 ts 中,所以 lua 会在 ts 的元表中查找 "__call"。
- ts 的元表中的 "__call" 键返回 Set.call。
- 现在会调用 Set.call 方法并以 insert 为名称,导致返回 Set.insert 函数。
- 调用 Set.insert(ts, 5) 。
不是这样的,实际上是这样的:
当
ts
对象中不存在insert
时,Lua 会查找它的元表对象的__index
键值对。- 如果有
__index
并且它是一个表,Lua 会在其中查找insert
。 - 如果有
__index
并且它是一个函数,Lua 会用原始的表(即ts
)以及被查找的键值(即insert
)作为参数来调用它。 - 如果没有,就会被认为是
nil
。
- 如果有
你遇到的错误是因为你的元表中没有设置 __index
,所以你实际上是在调用一个 nil
值。
如果你将要将方法存储在其中的话,可以通过将 __index
指向某个表(例如 Set
)来解决这个问题。
至于 __call
,它用于当你把对象作为函数来调用时。例如:
Set = {}
function Set.new(l)
local s = {}
setmetatable(s, {__index=Set, __call=Set.call})
for _, v in ipairs(l) do
s[v] = true
end
return s
end
function Set.call(s, f)
-- 收集集合中的每个元素,并每个元素调用一个函数
for k in pairs(s) do
f(k)
end
end
function Set.insert(t, v)
t[v] = true
end
ts = Set.new {1,2,3,4,5}
ts:insert(5)
ts:insert(6)
ts(print) -- 等价于 getmetatable(ts).__call(ts, print),即 Set.call(ts, print)
-- 根据 __call 和 __index 的设置,
-- 这个调用等价于上面的一行
ts:call(print)
现在我读这段代码的方式是:
- 当我调用ts:insert(5), 由于insert不存在被调用,这意味着ts元表将被搜索以寻找"__call"。
你的问题在这里。当调用表本身(即作为函数)时,会查询“__call”元方法:
local ts = {}
local mt = {}
function mt.__call(...)
print("Table called!", ...)
end
setmetatable(ts, mt)
ts() --> 打印“Table called!”
ts(5) --> 打印“Table called!”和5
ts“String construct-call” --> 打印“Table called!”和“String construct-call”
Lua中的面向对象冒号调用,如下所示:
ts:insert(5)
只是语法上的糖,表示为
ts.insert(ts,5)
它本身就是语法糖
ts [“insert”](ts,5)
因此,在ts
上采取的操作不是调用,而是索引(ts [“insert”]
的结果被调用),其受__index
元方法的控制。
对于简单的情况,__index
元方法可以是一个表,其中索引会“回退”到另一个表(请注意,它是元表中__index键的_value_被索引,_而不是元表本身):
local fallback = {example = 5}
local mt = {__index = fallback}
local ts = setmetatable({}, mt)
print(ts.example) --> 打印 5
作为函数的__index
元方法类似于您预期的带有Set.call的签名,只是它在键之前将被索引的表传递:
local ff = {}
local mt = {}
function ff.example(...)
print("Example called!",...)
end
function mt.__index(s,k)
print("Indexing table named:", s.name)
return ff[k]
end
local ts = {name = "Bob"}
setmetatable(ts, mt)
ts.example(5) --> 打印“索引表命名:”和“Bob”,
--> 然后在下一行打印“Example called!”和5
有关元表的更多信息,请参见手册。
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?
- addEventListener 返回 nil Lua
- Lua中获取用户配置主目录的跨平台方法
- 如何编写 Lua 模式将字符串(嵌套数组)转换为真正的数组?
Set = {} function Set.new(l) local s = {} setmetatable(s, {__index=Set}) for _, v in ipairs(l) do s[v] = true end return s end function Set.call(f) return Set[f] end function Set.insert(t, v) t[v] = true end ts = Set.new {1,2,3,4,5} ts:insert(5) ts:insert(6) for k in pairs(ts) do print(k) end
Set = {} -- 新建集合 function Set.new(l) local s = {} setmetatable(s, {__index=Set}) for _, v in ipairs(l) do s[v] = true end return s end -- 调用 Set 对象的方法 function Set.call(f) return Set[f] end -- 插入元素到集合中 function Set.insert(t, v) t[v] = true end ts = Set.new {1,2,3,4,5} -- 新建集合 ts:insert(5) -- 插入元素 5 ts:insert(6) -- 插入元素 6 for k in pairs(ts) do -- 遍历集合 print(k) end