Julia:通过给定的字符串调用函数

Yve*_*ves 12 reflection julia

朱莉娅是否像java一样支持反射?

我需要的是这样的:

str = ARGS[1] # str is a string
# invoke the function str()
Run Code Online (Sandbox Code Playgroud)

Ste*_*ski 27

好方法

建议的方法是将函数名称转换为符号,然后在相应的名称空间中查找该符号:

julia> fn = "time"
"time"

julia> Symbol(fn)
:time

julia> getfield(Main, Symbol(fn))
time (generic function with 2 methods)

julia> getfield(Main, Symbol(fn))()
1.448981716732318e9
Run Code Online (Sandbox Code Playgroud)

您可以Main在此处更改为任何模块,仅查看该模块中的功能.这使您可以将可用功能集限制为仅适用于该模块中可用的功能集.您可以使用"裸模块"创建一个只包含您使用它填充的函数的命名空间,而不会Base默认导入所有名称.

坏道路

不推荐但许多人似乎首先要达到的另一种方法是为调用该函数的代码构造一个字符串,然后解析该字符串并对其进行评估.例如:

julia> eval(parse("$fn()")) # NOT RECOMMENDED
1.464877410113412e9
Run Code Online (Sandbox Code Playgroud)

虽然这很简单,但不建议使用,因为它既缓慢,易碎又危险.解析和评估代码本质上要复杂得多,因此比在模块中进行名称查找要慢 - 名称查找本质上只是一个哈希表查找.在朱莉娅,其中代码只是即时编译,而不是解释,EVAL是慢,更昂贵,因为它不只是涉及解析,同时也产生LLVM代码,运行优化过程,发射机器码,然后最后调用一个功能.解析和评估字符串也很脆弱,因为当代码转换为文本时,将丢弃所有预期的含义.例如,假设有人意外地提供了一个空函数名称 - 那么这个代码用于调用函数的事实完全会因语法的偶然相似性而丢失:

julia> fn = ""
""

julia> eval(parse("$fn()"))
()
Run Code Online (Sandbox Code Playgroud)

哎呀.这根本不是我们想要的.在这种情况下,行为是相当无害的,但它可能很容易变得更糟:

julia> fn = "println(\"rm -rf /important/directory\"); time"
"println(\"rm -rf /important/directory\"); time"

julia> eval(parse("$fn()"))
rm -rf /important/directory
1.448981974309033e9
Run Code Online (Sandbox Code Playgroud)

如果用户的输入不受信任,这是一个巨大的安全漏洞.即使您信任该用户,他们仍然可能意外地提供会做出意外和坏事的输入.名称查找方法避免了这些问题:

julia> getfield(Main, Symbol(fn))()
ERROR: UndefVarError: println("rm -rf /important/directory"); time not defined
 in eval(::Module, ::Any) at ./boot.jl:225
 in macro expansion at ./REPL.jl:92 [inlined]
 in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:46
Run Code Online (Sandbox Code Playgroud)

查找名称然后将其作为函数调用的意图是显式的,而不是在生成的字符串语法中隐式,因此在最坏的情况下会得到关于未定义的奇怪名称的错误.

性能

如果要在内部循环中调用动态指定的函数或作为某些递归计算的一部分,则getfield每次调用函数时都要避免执行查找.在这种情况下,您需要做的就是const在定义调用它的迭代/递归过程之前绑定到动态指定的函数.例如:

fn = "deg2rad" # converts angles in degrees to radians

const f = getfield(Main, Symbol(fn))

function fast(n)
    t = 0.0
    for i = 1:n
        t += f(i)
    end
    return t
end

julia> @time fast(10^6) # once for JIT compilation
  0.010055 seconds (2.97 k allocations: 142.459 KB)
8.72665498661791e9

julia> @time fast(10^6) # now it's fast
  0.003055 seconds (6 allocations: 192 bytes)
8.72665498661791e9

julia> @time fast(10^6) # see?
  0.002952 seconds (6 allocations: 192 bytes)
8.72665498661791e9
Run Code Online (Sandbox Code Playgroud)

结合f必须以获得最佳性能不变,否则编译器无法知道你会不会改变f在任何时候,在其他功能点(或者甚至一些,这不是一个函数),所以它必须发出代码看起来f最多动态地进行每次循环迭代 - 实际上就像getfield在循环中手动调用一样.在这里,因为fconst,编译器知道f不能改变所以它可以发出只是直接调用正确函数的快速代码.但编译器有时甚至可以做得更好 - 在这种情况下,它实际上是内联deg2rad函数的实现,这只是乘以pi/180:

julia> @code_llvm fast(100000)

define double @julia_fast_51089(i64) #0 {
top:
  %1 = icmp slt i64 %0, 1
  br i1 %1, label %L2, label %if.preheader

if.preheader:                                     ; preds = %top
  br label %if

L2.loopexit:                                      ; preds = %if
  br label %L2

L2:                                               ; preds = %L2.loopexit, %top
  %t.0.lcssa = phi double [ 0.000000e+00, %top ], [ %5, %L2.loopexit ]
  ret double %t.0.lcssa

if:                                               ; preds = %if.preheader, %if
  %t.04 = phi double [ %5, %if ], [ 0.000000e+00, %if.preheader ]
  %"#temp#.03" = phi i64 [ %2, %if ], [ 1, %if.preheader ]
  %2 = add i64 %"#temp#.03", 1
  %3 = sitofp i64 %"#temp#.03" to double
  %4 = fmul double %3, 0x3F91DF46A2529D39         ; deg2rad(x) = x*(pi/180)
  %5 = fadd double %t.04, %4
  %6 = icmp eq i64 %"#temp#.03", %0
  br i1 %6, label %L2.loopexit, label %if
}
Run Code Online (Sandbox Code Playgroud)

如果您需要使用许多不同的动态指定函数并且使用Julia 0.5(每晚),那么您甚至可以将要调用的函数作为参数传递:

function fast(f,n)
    t = 0.0
    for i = 1:n
        t += f(i)
    end
    return t
end

julia> @time fast(getfield(Main, Symbol(fn)), 10^6)
  0.007483 seconds (1.70 k allocations: 76.670 KB)
8.72665498661791e9

julia> @time fast(getfield(Main, Symbol(fn)), 10^6)
  0.002908 seconds (6 allocations: 192 bytes)
8.72665498661791e9
Run Code Online (Sandbox Code Playgroud)

这会生成与fast上面单个参数相同的快速代码,但会为f您调用它的每个不同函数生成一个新版本.

  • 谢谢你.我知道必须有更好的方法. (3认同)
  • 作为 Julia 的新手,我发现这是一个超级答案! (3认同)