如何在Go语言中获取函数名称?

140

给定一个函数,是否可能获取其名称?例如:

func foo() {
}

func GetFunctionName(i interface{}) string {
    // ...
}

func main() {
    // Will print "name: foo"
    fmt.Println("name:", GetFunctionName(foo))
}

有人告诉我可以使用runtime.FuncForPC函数,但是我不太明白如何使用它。

4个回答

258
我找到了解决方案:
package main

import (
    "fmt"
    "reflect"
    "runtime"
)

func foo() {
}

func GetFunctionName(i interface{}) string {
    return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}

func main() {
    // This will print "name: main.foo"
    fmt.Println("name:", GetFunctionName(foo))
}

4
虽然这似乎能够实现目标,但在此需要注意一些问题:.Pointer() 的文档声明 "如果 v 的 Kind 是 Func,则返回的指针是底层代码指针,但未必足以唯一标识单个函数。唯一的保证是,当且仅当 v 是 nil func Value 时,结果为零。" - jochen
1
@jochen的“not a single func”是指它可能会返回假阳性(即不同函数的指针)吗? - themihai
1
@themihai 我不知道,我引用的那句话是 https://golang.org/pkg/reflect/#Value.Pointer 上所有文档中提到的。但是这句话似乎表明,对于不同的函数,可以获得相同的指针,是吗?如果是这样的话,GetFunctionName 可能会为不同的函数返回相同的名称? - jochen
5
我认为这与闭包有关;如果你创建了两个闭包,它们具有相同的底层函数,则它们将是等效的,即使它们所封闭的值不同。 - Alex Guerra
当在泛型结构的方法上使用此方法时,会出现一些问题:https://github.com/golang/go/issues/63945 - undefined

15

这不完全是您想要的,因为它记录了文件名和行号,但以下是我在我的Tideland Common Go库(http://tideland-cgl.googlecode.com/)中使用"runtime"包的方法:

// Debug prints a debug information to the log with file and line.
func Debug(format string, a ...interface{}) {
    _, file, line, _ := runtime.Caller(1)
    info := fmt.Sprintf(format, a...)

    log.Printf("[cgl] debug %s:%d %v", file, line, info)

5
那并没有真正帮到我。我不需要获取关于调用栈的信息,而是需要获取有关特定函数的信息。如果可以的话,我相信如果我知道如何通过函数引用获得相应的程序计数器(pc),问题将会得到回答。 - moraes

4
我找到了更好的解决方案,只需要在下面的函数中传递一个函数,输出将会变得简单明了。
package main

import (
    "reflect"
    "runtime"
    "strings"
)

func GetFunctionName(temp interface{}) string {
    strs := strings.Split((runtime.FuncForPC(reflect.ValueOf(temp).Pointer()).Name()), ".")
    return strs[len(strs)-1]
}

以下是使用示例:

package main

import "fmt"

func main() {
    fmt.Println(GetFunctionName(main))
}

以下是您应该期望得到的答案:

main

1
这有什么意义呢?你只是返回了传递给它的相同字符串。最好直接使用 fmt.Println("main") - Gostega
3
@Gostega,我传递的是一个函数,而不是一个字符串... 这个解决方案实际上就是问题所需的。 请再次阅读问题。 - Mahmood
你是对的,问题确实是这样问的,我道歉。我认为我在问题中读出了一些含义,因为按照原文写的话,它对我来说毫无意义。通过传递函数名来获取函数的名称似乎完全没有意义。如果已经有了名称,只需打印或使用它,为什么要将该名称传递给另一个函数,该函数只是将相同的名称返回。 - Gostega
我寻找答案的原因是,你可以编写一个库,期望用户传递一个函数,并使用该函数的名称来进行反馈。例如,测试框架可以使用名称来标识测试名称。 - Daniël Sonck

3
通过获取上一个调用者的函数名称:
import (
    "os"
    "runtime"
)

func currentFunction() string {
    counter, _, _, success := runtime.Caller(1)

    if !success {
        println("functionName: runtime.Caller: failed")
        os.Exit(1)
    }

    return runtime.FuncForPC(counter).Name()
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接