【问题标题】:julia introspection - get name of variable passed to functionjulia introspection - 获取传递给函数的变量名称
【发布时间】:2015-12-31 22:11:20
【问题描述】:

在 Julia 中,有什么方法可以获取传递给函数的名称?

x = 10
function myfunc(a)
# do something here
end
assert(myfunc(x) == "x")

我需要使用宏还是有提供自省的本机方法?

【问题讨论】:

  • 不,这是不可能的。我不知道有任何主流语言是一个好主意(甚至可以使用)。这可能有助于澄清您最终要解决的问题...
  • 在 Matlab 或 Octave IDE 中定义变量时,当前模块/作用域中的变量会显示在侧面板中,这有助于了解工作空间中存在哪些变量。在 julia 中,可以通过 names(current_modules()) 获取所有变量
  • 一个接受函数定义的宏将使这成为可能(通过插入从参数表达式计算的本地定义)
  • @ejang 那些自省功能是在函数体之外实现的,例如通过在解析或语法降低的某个阶段挂钩,或者通过生成有关可以发现和显示的函数的元信息调试器(例如,DWARF)。

标签: julia


【解决方案1】:

你可以用宏来获取变量名:

julia> macro mymacro(arg)
           string(arg)
       end

julia> @mymacro(x)
"x"

julia> @assert(@mymacro(x) == "x")

但正如其他人所说,我不确定你为什么需要它。

宏在编译期间对 AST(代码树)进行操作,x 作为符号 :x 传递到宏中。您可以将符号转换为字符串,反之亦然。宏将代码替换为代码,因此@mymacro(x) 被简单地拉出并替换为string(:x)

【讨论】:

  • 我发现这对于在本质上类似于 assert 宏但我不想抛出错误的信息警告消息很有用。按照docs.julialang.org/en/stable/manual/… 的 julia 文档并根据您的需要修改宏
【解决方案2】:

好吧,自相矛盾:从技术上讲在一个(相当有限的)条件下,这是可能的:函数名称必须只有一个方法签名。这个想法与Python 的此类问题的答案非常相似。在演示之前,我必须强调这些是内部编译器细节,可能会发生变化。简要说明:

julia> function foo(x)
           bt = backtrace()
           fobj = eval(current_module(), symbol(Profile.lookup(bt[3]).func))
           Base.arg_decl_parts(fobj.env.defs)[2][1][1]
       end
foo (generic function with 1 method)

julia> foo(1)
"x"

让我再次强调 这是一个坏主意,不应该用于任何事情! (好吧,除了回溯显示)。这基本上是“愚蠢的编译器技巧”,但我展示它是因为玩这些对象可能是一种教育,并且解释确实为@ejang 的澄清评论提供了更有用的答案。

解释:

  • bt = backtrace() 从当前位置生成 ... 回溯 ...。 bt 是一个指针数组,其中每个指针是当前调用堆栈中的一个帧的地址。
  • Profile.lookup(bt[3]) 返回一个带有函数名称的 LineInfo 对象(以及有关每个帧的其他一些详细信息)。请注意,bt[1]bt[2] 本身就在 backtrace-generation 函数中,因此我们需要进一步向上堆栈以获取调用者。
  • Profile.lookup(...).func 返回函数名(符号:foo
  • eval(current_module(), Profile.lookup(...)) 返回与current_module() 中的名称:foo 关联的函数对象。如果我们修改function foo的定义返回fobj,那么注意REPL中foo对象的等价性:

    julia> function foo(x)
               bt = backtrace()
               fobj = eval(current_module(), symbol(Profile.lookup(bt[3]).func))
           end
    foo (generic function with 1 method)
    
    julia> foo(1) == foo
    true
    
  • fobj.env.defsMethodTable 返回第一个Method 条目,用于foo/fobj

  • Base.decl_arg_parts 是一个辅助函数(在methodshow.jl 中定义),它从给定的Method 中提取参数信息。
  • 索引的其余部分深入到参数的名称。

关于函数只有一个方法签名的限制,原因是MethodTable中会列出多个签名(见defs.next)。据我所知,目前没有公开的接口来获取与给定帧地址关联的特定方法。 (作为高级读者的练习:这样做的一种方法是修改jl_getFunctionInfo 中的地址查找功能,以同时返回损坏的函数名称,然后可以将其与特定的方法调用重新关联;但是,我不要认为我们目前存储了来自重命名的反向映射 -> 方法)。

还要注意 (1) 回溯很慢 (2) 在 Julia 中没有“函数局部” eval 的概念,所以即使有变量名,我相信也无法实际访问变量 (并且编译器可能会完全忽略未使用或其他方式的局部变量,将它们放入寄存器等)

至于在 cmets 中提到的 IDE 风格的自省使用:foo.env.defs 如上所示是“对象自省”的一个起点。在调试方面,Gallium.jl 可以检查给定帧中的 DWARF 局部变量信息。最后,JuliaParser.jl 是 Julia 解析器的纯 Julia 实现,它在多个 IDE 中被积极使用,以从高层次上自省代码块。

【讨论】:

  • 我们也可以使用这些技巧制作上面的Tom's macro 的通用版本:macro funcargs(func); fobj = eval(current_module(), func) ;Base.arg_decl_parts(fobj.env.defs)[2][1]; end .....(在没有eval 的情况下这样做作为练习!)
  • 我相信这实际上并不能解决@ejang 正在研究的问题。在原始帖子中,传递变量 x 的名称是所需的输出,而不是函数参数名称。问题 reprex 和这个 reprex 之间有点混淆,因为 x 以不同的方式使用。我在类似的情况下,当myfunc() 在某处被重复调用并且多次​​成功调用时,这样的响应很有用。发生故障时,您想知道是什么输入导致了它,因此在错误消息中包含传递的输入名称很有帮助。
  • @DanielEgan 调试器会帮忙吗?:julialang.org/blog/2019/03/debuggers ...我确实误解了这个问题(与类似问题的模式匹配太快)。我想您可以通过查看回溯然后解析调用位置中的原始源代码(使用例如 CSTParser.jl)来完成所要求的操作。但这仍然很棘手:基本问题在于,按照设计,函数不应该对其调用环境进行推理。像 R 这样的一些语言确实支持这样的特性,这可能是这类问题的起源;但该功能是有成本的。
【解决方案3】:

另一种方法是使用函数的vinfo。这是一个例子:

function test(argx::Int64)
    vinfo = code_lowered(test,(Int64,))
    string(vinfo[1].args[1][1])
end
test (generic function with 1 method)

julia> test(10)
"argx"

以上取决于知道函数的签名,但如果它是在函数本身中编码的,这不是问题(否则可能需要一些宏魔法)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-21
    • 1970-01-01
    • 2012-04-13
    • 2014-08-13
    • 1970-01-01
    • 2011-02-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多