Lua debug.getinfo(1,“n”).name导致的奇怪行为

Lua debug.getinfo(1,“n”).name导致的奇怪行为,lua,Lua,我学习了如何使用debug.getinfo(1,“n”).name在函数中获取函数名 使用此功能,我发现了Lua中的奇怪行为 这是我的密码: function myFunc() local name = debug.getinfo(1, "n").name return name end function foo() return myFunc() end function boo() local name = myFunc() return name end prin

我学习了如何使用
debug.getinfo(1,“n”).name
在函数中获取函数名

使用此功能,我发现了Lua中的奇怪行为

这是我的密码:

function myFunc()
  local name = debug.getinfo(1, "n").name
  return name
end

function foo()
  return myFunc()
end

function boo()
  local name = myFunc()
  return name
end

print(foo())
print(boo())
结果:

nil
myFunc
如您所见,函数
foo()
boo()
调用相同的函数
myFunc()
,但它们返回不同的结果

如果我用其他字符串替换
debug.getinfo(1,“n”).name
,它们将返回与预期相同的结果,但我不理解使用
debug.getinfo()
导致的意外行为

是否可以更正
myFunc()
函数,以便调用
foo()
boo()
函数返回相同的结果

预期结果:

myFunc
myFunc
这是Lua所做的工作的结果

在这种情况下,Lua将函数调用转换为“goto”语句,并且不使用任何额外的堆栈帧来执行尾部调用

您可以添加
traceback
语句来检查它:

函数myFunc()
本地名称=debug.getinfo(1,“n”).name
打印(debug.traceback(“堆栈跟踪”))
返回名称
结束
当您返回函数调用时,在Lua中进行尾部调用优化:

——优化
函数1()
返回测试()
结束
--优化
函数2()
返回测试(foo(),bar(5+baz())
结束
--未优化
函数bad1()
返回测试()+1
结束
--未优化
函数bad2()
返回测试()[2]+foo()
结束
有关详细信息,请参阅以下链接: -
-

您可以通过
luac-l-p

...

function <stdin:6,8> (4 instructions at 0x555f561592a0)
0 params, 2 slots, 1 upvalue, 0 locals, 1 constant, 0 functions
  1 [7] GETTABUP    0 0 -1  ; _ENV "myFunc"
  2 [7] TAILCALL    0 1 0
  3 [7] RETURN      0 0
  4 [8] RETURN      0 1

function <stdin:10,13> (4 instructions at 0x555f561593b0)
0 params, 2 slots, 1 upvalue, 1 local, 1 constant, 0 functions
  1 [11]    GETTABUP    0 0 -1  ; _ENV "myFunc"
  2 [11]    CALL        0 1 2
  3 [12]    RETURN      0 2
  4 [13]    RETURN      0 1
是有效的C代码,但在Lua中最终会导致堆栈溢出

local function recursive(n) return recursive(n+1) end
将永远运行。两者都是尾部调用,但只有第二个得到优化



编辑:和C一样,一些编译器可能会自己实现尾部调用优化,所以不要到处告诉每个人“C永远不会这样做”;它只是语言中不需要的一部分,而在Lua中,它实际上是在语言规范中定义的,因此在它具有TCO之前它不是Lua。

在Lua中,任何形式为
return(…)
的返回语句都是“尾部调用”。尾部调用基本上不存在于调用堆栈中,因此它们不占用额外的空间或资源。您调用的函数将有效地从调试信息中删除

是否可以更正
myFunc()
函数,以便调用
foo()
boo()
函数返回相同的结果

嗯。。。是的,但在我告诉你怎么做之前,请允许我试着说服你不要这样做

如前所述,尾部调用是Lua语言的一部分。从堆栈中删除尾部调用并不是一种“优化”,就像使用
break
时退出
for
循环一样。它是Lua语法的一部分,Lua程序员有权期望尾部调用是尾部调用,就像他们有权期望
break
退出循环一样

作为一种语言,Lua明确指出:

local function recursive(...)
  --some terminating condition

  return recursive(modified_args)
end
永远不会耗尽堆栈空间。它将与执行循环一样节省堆栈空间。这是Lua语言的一部分,就像
for
while
的行为一样

如果用户希望通过尾部调用调用您的函数,这是他们作为语言用户的权利,这种语言使尾部调用成为一种东西。剥夺语言使用者使用该语言特征的权利是不礼貌的

所以不要这样做

此外,代码表明您正试图依赖具有名称的函数。你正在用这些名字做一些有意义的事情

嗯,Lua不是Python;Lua函数不必有名称、句点。因此,您不应该编写有意义地依赖于函数名的代码。出于调试或记录目的,可以。但是,您不应该仅仅为了调试和记录而打破用户的期望。因此,如果用户做了一个尾部调用,只需接受这是用户想要的,您的调试/日志记录将受到轻微影响

好的,那么,我们同意你不应该这样做吗?Lua用户有权跟踪呼叫,而你没有权利拒绝他们?Lua函数没有命名,您不应该编写要求它们维护名称的代码?好吗


下面是你永远不应该使用的糟糕代码(在Lua 5.3中):

然后,只需将实际函数替换为旁路的返回:

function myFunc()
  local name = debug.getinfo(1, "n").name
  return name
end

myFunc = bypass_tail_call(myFunc)
请注意,旁路函数必须构建一个数组来保存返回值,然后将它们解压缩到最终的return语句中。这显然需要额外的内存分配,而这在常规代码中不必发生


因此,还有另一个不这样做的原因。

那么,仅仅纠正
myFunc()
So`boo()`和
foo()
就不可能得到同样的结果吗?@ZackLee不。从技术上讲,他们已经做到了。
debug
库中的函数是特殊的;它们让你洞察到你通常不应该关心的事情。您的程序永远不应该将其实际行为建立在调试库中的任何内容上,因为它揭示了Lua出于正当理由通常对您隐藏的东西。
function bypass_tail_call(Func)
    local function tail_call_bypass(...)
        local rets = table.pack(Func(...))
        return table.unpack(rets, rets.n)
    end
    return tail_call_bypass
end
function myFunc()
  local name = debug.getinfo(1, "n").name
  return name
end

myFunc = bypass_tail_call(myFunc)